Compare commits

..

600 Commits

Author SHA1 Message Date
Rob Bradford
52b839694d build, release-notes: Document 0.8.0 release
Update the build version and the release notes for the 0.8.0 release.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-06-11 18:31:31 +01:00
Rob Bradford
776f8fc555 build: Update Cargo.lock
An update to the Cargo.lock file was missing from a previous commit.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-06-11 16:58:35 +02:00
Michael Zhao
3f18f93f40 docs: Add a guide for testing on AArch64
The support of AArch64 is in very early stage. The steps in building and
runing on X86 and AArch64 can not align well yet. Adding AArch64 content
to README.md would produce much divergence.
Adding a guide in docs/ folder could be a better way to start now.

Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-06-11 15:00:17 +01:00
Michael Zhao
97a1e5e1d2 vmm: Exit VMM event loop after guest shutdown for AArch64
X86 and AArch64 work in different ways to shutdown a VM.
X86 exit VMM event loop through ACPI device;
AArch64 need to exit from CPU loop of a SystemEvent.

Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-06-11 15:00:17 +01:00
Michael Zhao
5cd1730bc4 vmm: Configure VM on AArch64
Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-06-11 15:00:17 +01:00
Michael Zhao
917219fa92 vmm: Enable VCPU for AArch64
Added MPIDR which is needed in system configuration.

Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-06-11 15:00:17 +01:00
Michael Zhao
b5f1c912d6 vmm: Enable memory manager for AArch64
Screened IO space as it is not available on AArch64.

Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-06-11 15:00:17 +01:00
Michael Zhao
eeeb45bbb9 vmm: Enable device manager for AArch64
Screened IO bus because it is not for AArch64.
Enabled Serial, RTC and Virtio devices with MMIO transport option.

Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-06-11 15:00:17 +01:00
Michael Zhao
e9488846f1 vm-allocator: Enable vm-allocator for AArch64
Implemented GSI allocator and system allocator for AArch64.
Renamed some layout definitions to align more code between architectures.

Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-06-11 15:00:17 +01:00
Anatol Belski
5343b0ac18 net_util: Fix usage of deprecated mac_address method
This fixes the build warning below

warning: use of deprecated item 'tap::tests::pnet::datalink::NetworkInterface::mac_address': Please use NetworkInterface's field 'mac' instead.
   --> net_util/src/tap.rs:530:24
    |
530 |             (interface.mac_address(), tx, rx)
    |                        ^^^^^^^^^^^
    |
    = note: `#[warn(deprecated)]` on by default

Signed-off-by: Anatol Belski <ab@php.net>
2020-06-11 08:23:01 +01:00
Sebastien Boeuf
bf37ebdcb6 arch: x86_64: Add 5th level of paging when needed
For correctness, when the CPUID supports the LA57 feature, the VMM sets
the CR4.LA57 register, which means a fifth level of page table might be
needed. Even if it's not needed because the kernel should not use
addresses over 1GiB, it's better to define this new level anyway.

This patch only applies to the Linux boot codepath, which means it
affects both vmlinux without PVH and bzImage binaries. The bzImage
does not need this since the page tables and CR4 registers are set in
the decompression code from the kernel.

And for vmlinux with PVH, if we follow the PVH specification, the kernel
must be responsible for setting things up, but the implementation is
missing. This means for now that PVH does not support LA57 with 5 levels
of paging.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-06-11 09:18:52 +02:00
Anatol Belski
abd6204d27 source: Fix file permissions
Rust sources and some data files should not be executable. The perms are
set to 644.

Signed-off-by: Anatol Belski <ab@php.net>
2020-06-10 18:47:27 +01:00
Rob Bradford
02ac1820b1 scripts: Ensure musl-gcc is used by musl build
"cc" is invoked as part of the Cloud Hypervisor Rust build however due
to a copy and paste error the wrong variable was being tested for
overriding the CC and the CFLAGS.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-06-10 18:22:50 +02:00
Bo Chen
cc85d896a5 tests: Extend test_*_reboot with checks on fd leaking
This revised version of the patch reuses the back-off strategy from
'ssh_command()' to deal with varying booting time.

Fixes: #1209

Signed-off-by: Bo Chen <chen.bo@intel.com>
2020-06-10 13:54:18 +01:00
dependabot-preview[bot]
2ae547cf71 build(deps): bump vmm-sys-util from 0.6.0 to 0.6.1
Bumps [vmm-sys-util](https://github.com/rust-vmm/vmm-sys-util) from 0.6.0 to 0.6.1.
- [Release notes](https://github.com/rust-vmm/vmm-sys-util/releases)
- [Changelog](https://github.com/rust-vmm/vmm-sys-util/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-vmm/vmm-sys-util/compare/v0.6.0...v0.6.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-10 13:24:53 +01:00
dependabot-preview[bot]
f3556279d5 build(deps): bump serde_json from 1.0.54 to 1.0.55
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.54 to 1.0.55.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.54...v1.0.55)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-10 11:48:39 +01:00
Rob Bradford
dc034eb3b0 scripts: Only use musl for the Rust components
Don't use the musl toolchain for the virtiofsd build as it does not
work.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-06-10 10:16:57 +01:00
Rob Bradford
176d671609 build: Run musl builds in parallel to glibc builds
Run these builds against every PR rather than just master.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-06-10 10:16:57 +01:00
dependabot-preview[bot]
083189e5a1 build(deps): bump vcpkg from 0.2.9 to 0.2.10
Bumps [vcpkg](https://github.com/mcgoo/vcpkg-rs) from 0.2.9 to 0.2.10.
- [Release notes](https://github.com/mcgoo/vcpkg-rs/releases)
- [Changelog](https://github.com/mcgoo/vcpkg-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mcgoo/vcpkg-rs/compare/vcpkg-rs-0.2.9...vcpkg-rs-0.2.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-10 09:21:10 +02:00
dependabot-preview[bot]
2334b521da build(deps): bump syn from 1.0.30 to 1.0.31
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.30 to 1.0.31.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.30...1.0.31)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-10 07:35:29 +02:00
dependabot-preview[bot]
99c99c2476 build(deps): bump serde_json from 1.0.53 to 1.0.54
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.53 to 1.0.54.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.53...v1.0.54)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-09 21:17:48 +01:00
Sebastien Boeuf
96a5e22bd6 resources: kernel: Enable 5 levels of page table
Some CPUs might support up to 52 bits of addressable space, that's the
reason why we need the guest kernel to support 5 levels of page table.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-06-09 18:08:09 +01:00
Sebastien Boeuf
653087d7a3 vmm: Reduce MMIO address space by 4KiB
In order to workaround a Linux bug that happens when we place devices at
the end of the physical address space on recent hardware (52 bits limit)
we reduce the MMIO address space by one 4k page. This way, nothing gets
allocated in the last 4k of the address space, which is negligible given
the amount of space in the address space.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-06-09 18:08:09 +01:00
Sebastien Boeuf
5f0b620148 arch: x86_64: Enable CR4 LA57 feature
In case the host CPU exposes the support for LA57 feature through its
cpuid, the CR4.LA57 bit is enabled accordingly.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-06-09 18:08:09 +01:00
Rob Bradford
09fd325963 build: Use fork of vm-memory with less performance impact
Currently released vm-memory uses aligned and volatile copying for all
data. The version in the fork only uses the assured (and slower) path
for data upto the natural data width.

Fixes: #1258

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-06-09 17:06:35 +02:00
Henry Wang
5f9e079a03 device: Add AArch64 RTC PL031 implementation
This commit adds the implementation for the AArch64 PL031
Real Time Clock (RTC) that provides a long time base counter. This
is achieved by generating an interrupt signal after counting a
programmed number of cycles of a real-time clock input. The AArch64
guest VM of the cloud-hypervisor will use this RTC to sync the time
in itself.

Signed-off-by: Henry Wang <Henry.Wang@arm.com>
2020-06-09 06:27:46 +01:00
Bo Chen
625bab69bd vmm: api: Allow to delete non-booted VMs
The action of "vm.delete" should not report errors on non-booted
VMs. This patch also revised the "docs/api.md" to reflect the right
'Prerequisites' of different API actions, e.g. on "vm.delete" and
"vm.boot".

Fixes: #1110

Signed-off-by: Bo Chen <chen.bo@intel.com>
2020-06-09 05:58:32 +01:00
LiYa'nan
313883f6e4 remove duplicated structure InitrdConfig
structure InitrdConfig duplicated with InitramfsConfig

Signed-off-by: LiYa'nan <oliverliyn@gmail.com>
2020-06-09 05:53:37 +01:00
dependabot-preview[bot]
afe60808ac build(deps): bump synstructure from 0.12.3 to 0.12.4
Bumps [synstructure](https://github.com/mystor/synstructure) from 0.12.3 to 0.12.4.
- [Release notes](https://github.com/mystor/synstructure/releases)
- [Commits](https://github.com/mystor/synstructure/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-08 21:05:16 +01:00
Rob Bradford
aa79a92c35 tests: Add integration test for unprivileged network
This tests whether we can have a working network without having
CAP_NET_ADMIN.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-06-08 17:56:10 +02:00
Rob Bradford
9b71ba20ac vmm, vm-virtio: Stop always autogenerating a host MAC address
This removes the need to use CAP_NET_ADMIN privileges and instead the
host MAC addres is either provided by the user or alternatively it is
retrieved from the kernel.

TEST=Run cloud-hypervisor without CAP_NET_ADMIN permission and a
preconfigured tap device:

sudo ip tuntap add name tap0 mode tap
sudo ifconfig tap0 192.168.249.1 netmask 255.255.255.0 up
cargo clean
cargo build
target/debug/cloud-hypervisor --serial tty --console off --kernel ~/src/rust-hypervisor-firmware/target/target/release/hypervisor-fw --disk path=~/workloads/clear-33190-kvm.img --net tap=tap0

VM was also rebooted to check that works correctly.

Fixes: #1274

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-06-08 17:56:10 +02:00
Rob Bradford
1f8b6fa947 net_util: Allow retrieving the MAC address from the TAP device
This can be used to preserve the host MAC address as part of the
configuration when the TAP device is precreated.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-06-08 17:56:10 +02:00
Rob Bradford
929d70bc7f net_util: Only try and enable the TAP device if it not already enabled
This allows an existing TAP interface to be used without needing
CAP_NET_ADMIN permissions on the Cloud Hypervisor binary as the ioctl to
bring up the interface is avoided.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-06-08 17:56:10 +02:00
Bo Chen
eda9bfc7a1 vhost_user_fs: Replace the '--sock' parameter with '--socket'
We are keeping the '--sock' parameter for backward compatibility.

Fixes: #1091

Signed-off-by: Bo Chen <chen.bo@intel.com>
2020-06-08 17:41:12 +02:00
Bo Chen
a8cdf2f070 tests,vm-virtio,vmm: Use 'socket' for all CLI/API parameters
This patch unifies the inconsistent uses of 'socket' and 'sock' from our
CLI/API parameters.

Fixes: #1091

Signed-off-by: Bo Chen <chen.bo@intel.com>
2020-06-08 17:41:12 +02:00
Rob Bradford
90e7accf8b ch-remote: Show response body from error
If the server returns an error then print out the response body if there
is one present.

Fixes: #1262

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-06-08 17:35:54 +02:00
Henry Wang
e436bbf3bb build: Install libfdt in github cross-build workflow
In AArch64 prototype, there are code to construct the flattened device
tree, and to make such code compilable we need to install libfdt-dev. In
normal situation, this installation process can be done by either installing
libfdt-dev locally or in the development container.

Before formal AArch64 CI is setup, we use the workaround in this commit
to install libfdt in the github cross-build workflow.

Signed-off-by: Henry Wang <Henry.Wang@arm.com>
2020-06-08 09:59:46 +01:00
Henry Wang
2d13751d7d aarch64: Porting fdt related files from Firecracker
When booting VM on AArch64 machines, we need to construct the
flattened device tree before loading kernel. Hence here we add
the implementation of the flattened device tree for AArch64.

Signed-off-by: Henry Wang <Henry.Wang@arm.com>
2020-06-08 09:59:46 +01:00
Henry Wang
5a18dd36e2 aarch64: Porting AArch64 register implementation from Firecracker
As on AArch64 systems we need register mpidr to create the
flattened device tree, here in this commit we add ported AArch64
register implementation from Firecracker and related changes to
make this commit build.

Signed-off-by: Henry Wang <Henry.Wang@arm.com>
2020-06-08 09:59:46 +01:00
Henry Wang
d605fda3f7 aarch64: Porting GIC source files from Firecracker
This commit adds ported code of Generic Interrupt Controller (GIC)
software implementation for AArch64, including both GICv2 and
GICv3 devices. These GIC devices are actually emulated by the
host kernel through KVM and will be used in the guest VM as the
interrupt controller for AArch64.

Signed-off-by: Henry Wang <Henry.Wang@arm.com>
2020-06-08 09:59:46 +01:00
Michael Zhao
ce624a6dee aarch64: Add memory layout for AArch64
This commit adds the memory layout design for AArch64 in
`arch/src/aarch64/layout.rs` and related changes in
`arch/src/lib.rs` to make sure this commit can build.

Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-06-08 09:59:46 +01:00
dependabot-preview[bot]
c7d44b880e build(deps): bump quote from 1.0.6 to 1.0.7
Bumps [quote](https://github.com/dtolnay/quote) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.6...1.0.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-08 08:05:11 +02:00
dependabot-preview[bot]
7c91dfaeef build(deps): bump proc-macro-nested from 0.1.4 to 0.1.5
Bumps [proc-macro-nested](https://github.com/dtolnay/proc-macro-hack) from 0.1.4 to 0.1.5.
- [Release notes](https://github.com/dtolnay/proc-macro-hack/releases)
- [Commits](https://github.com/dtolnay/proc-macro-hack/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-08 08:04:48 +02:00
dependabot-preview[bot]
17c16e5c82 build(deps): bump pin-project from 0.4.19 to 0.4.20
Bumps [pin-project](https://github.com/taiki-e/pin-project) from 0.4.19 to 0.4.20.
- [Release notes](https://github.com/taiki-e/pin-project/releases)
- [Changelog](https://github.com/taiki-e/pin-project/blob/master/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/pin-project/compare/v0.4.19...v0.4.20)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-07 17:00:42 +01:00
dependabot-preview[bot]
a2398742da build(deps): bump arc-swap from 0.4.6 to 0.4.7
Bumps [arc-swap](https://github.com/vorner/arc-swap) from 0.4.6 to 0.4.7.
- [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.6...v0.4.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-07 17:00:31 +01:00
dependabot-preview[bot]
b31fe72edc build(deps): bump openssl-sys from 0.9.57 to 0.9.58
Bumps [openssl-sys](https://github.com/sfackler/rust-openssl) from 0.9.57 to 0.9.58.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-sys-v0.9.57...openssl-sys-v0.9.58)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-06 09:50:31 +01:00
dependabot-preview[bot]
9649700420 build(deps): bump dirs-sys from 0.3.4 to 0.3.5
Bumps [dirs-sys](https://github.com/dirs-dev/dirs-sys-rs) from 0.3.4 to 0.3.5.
- [Release notes](https://github.com/dirs-dev/dirs-sys-rs/releases)
- [Commits](https://github.com/dirs-dev/dirs-sys-rs/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-05 19:41:36 +02:00
Rob Bradford
eabf43fbf6 Revert "tests: Extend test_*_reboot with checks on fd leaking"
This reverts commit 7dc4e91303.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-06-05 14:09:17 +01:00
Bo Chen
7dc4e91303 tests: Extend test_*_reboot with checks on fd leaking
Fixes: #1209

Signed-off-by: Bo Chen <chen.bo@intel.com>
2020-06-04 20:48:44 +01:00
dependabot-preview[bot]
601d898f8a build(deps): bump pin-project from 0.4.17 to 0.4.19
Bumps [pin-project](https://github.com/taiki-e/pin-project) from 0.4.17 to 0.4.19.
- [Release notes](https://github.com/taiki-e/pin-project/releases)
- [Changelog](https://github.com/taiki-e/pin-project/blob/master/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/pin-project/compare/v0.4.17...v0.4.19)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-04 18:44:30 +02:00
Arron Wang
6ff107afe4 vm-device: Switch to use get_host_address_range in vfio-ioctls
The API has change to use generic GuestMemory trait:
pub fn get_host_address_range<M: GuestMemory>(
    mem: &M,
    addr: GuestAddress,
    size: usize,
) -> Option<*mut u8> {

Signed-off-by: Arron Wang <arron.wang@intel.com>
2020-06-04 08:48:55 +02:00
Samuel Ortiz
3336e80192 vfio: Switch to the vfio-ioctls crate ch branch
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-06-04 08:48:55 +02:00
Samuel Ortiz
d24aa72d3e vfio: Rename to vfio-ioctls
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-06-04 08:48:55 +02:00
Samuel Ortiz
53ce529875 vfio: Move the PCI implementation to the PCI crate
There is a much stronger PCI dependency from vfio_pci.rs than a VFIO one
from pci/src/vfio.rs. It seems more natural to have the PCI specific
VFIO implementation in the PCI crate rather than the other way around.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-06-04 08:48:55 +02:00
Michael Zhao
8f7dc73562 vmm: Move Vcpu::configure() to arch crate
Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-06-03 11:27:29 +02:00
Michael Zhao
969e5e0b51 vmm: Split configure_system() from load_kernel() for x86_64
Now the flow of both architectures are aligned to:
1. load kernel
2. create VCPU's
3. configure system
4. start VCPU's

Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-06-03 11:27:29 +02:00
Michael Zhao
20cf21cd9d vmm: Change booting process to cover AArch64 requirements
Between X86 and AArch64, there is some difference in booting a VM:
- X86_64 can setup IOAPIC before creating any VCPU.
- AArch64 have to create VCPU's before creating GIC.

The old process is:
1. load_kernel()
    load kernel binary
    configure system
2. activate_vcpus()
    create & start VCPU's

So we need to separate "activate_vcpus" into "create_vcpus" and
"activate_vcpus" (to start vcpus only). Setup GIC and create FDT
between the 2 steps.

The new procedure is:
1. load_kernel()
    load kernel binary
    (X86_64) configure system
2. create VCPU's
3. (AArch64) setup GIC
4. (AArch64) configure system
5. start VCPU's

Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-06-03 11:27:29 +02:00
Rob Bradford
61aa4615e2 vhost_user_net: Implement VIRTIO_RING_F_EVENT_IDX
The general handling of VIRTIO_RING_F_EVENT_IDX is in the
vhost_user_backend functionality and the net specific handling is in the
NetQueuePair from virtio-net.

As such enabling for the vhost-user-net backend is just the case of
adding the feature.

Fixes: #789

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-06-03 08:28:49 +02:00
Rob Bradford
a4d377a066 vm-virtio: net: Implement VIRTIO_RING_F_EVENT_IDX
If VIRTIO_RING_F_EVENT_IDX is negotiated only generate suppress
interrupts if the guest has asked us to do so.

Fixes: #788

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-06-03 08:28:49 +02:00
Rob Bradford
f06970730b vm-virtio: net: Handle lost interrupts on restore
In some situations it is seen that the first interrupt sent to the guest
is lost upon a restore (due to the tap worker being awake ahead of the
vPUs).

This causes problems with VIRTIO_RING_F_EVENT_IDX interrupt suppression
as the guest will not be interrupted again in order to mitigate this we
always interrupt the guest until the device itself has been signalled by
the guest.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-06-03 08:28:49 +02:00
Rob Bradford
a5596020b3 vm-virtio: Add some info! level debugging interrupt generation
This was very helpful when debugging interrupt issues and will be useful
for the future.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-06-03 08:28:49 +02:00
Rob Bradford
cc51fdb8a7 vhost_user_net: Use NetQueuePair from vm-virtio
The logic for handling the networking queues can now be shared between
the version running in vhost-user-net and vm-virtio.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-06-02 13:26:52 +02:00
Rob Bradford
fcc62efc41 vm-virtio: net: Prepare NetQueuePair for use in vhost-user-net
This requires exposing the struct members and also using Option<..>
types for the main epoll fd and the memory as they are initialised later
in vhost-user-net.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-06-02 13:26:52 +02:00
Rob Bradford
2dbd11864e vm-virtio: net: Split network handling
Split handling of behaviour that is independent of the device itself so
that it can be reused in the vhost-user-net device.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-06-02 13:26:52 +02:00
Rob Bradford
237cb184b4 vm-virtio: net: Add further missing error reporting
Ensure that errors generated from rx_single_frame are propagated
correctly.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-06-02 13:26:52 +02:00
Rob Bradford
36d072e69c vm-virtio: Add error propagation for TAP listener (un)registration
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-06-02 13:26:52 +02:00
Rob Bradford
3151b5d82a vm-virtio: net: Refactor to support code reuse
Split out functions that work just on the TAP device and queues. Whilst
doing so also improve the error handling to return Results rather than
drop errors.

This change also addresses a bug where the TAP event suppression could
ineffectual because it was being enabled immediately after it may have
been disabled:

resume_rx -> rx_single_frame -> unregister_listener -> resume_rx ->
register_listener.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-06-02 13:26:52 +02:00
dependabot-preview[bot]
22be88d3fb build(deps): bump vfio-bindings from 887b3cf to f08cbcb
Bumps [vfio-bindings](https://github.com/rust-vmm/vfio-bindings) from `887b3cf` to `f08cbcb`.
- [Release notes](https://github.com/rust-vmm/vfio-bindings/releases)
- [Commits](887b3cf877...f08cbcbf40)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-02 11:54:09 +01:00
dependabot-preview[bot]
6121f4626b build(deps): bump vfio-bindings from 46ef9d4 to 887b3cf
Bumps [vfio-bindings](https://github.com/rust-vmm/vfio-bindings) from `46ef9d4` to `887b3cf`.
- [Release notes](https://github.com/rust-vmm/vfio-bindings/releases)
- [Commits](46ef9d418e...887b3cf877)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-02 09:22:29 +01:00
dependabot-preview[bot]
b731e63a8c build(deps): bump ryu from 1.0.4 to 1.0.5
Bumps [ryu](https://github.com/dtolnay/ryu) from 1.0.4 to 1.0.5.
- [Release notes](https://github.com/dtolnay/ryu/releases)
- [Commits](https://github.com/dtolnay/ryu/compare/1.0.4...1.0.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-01 08:51:44 +01:00
dependabot-preview[bot]
d2d5ccb163 build(deps): bump proc-macro2 from 1.0.17 to 1.0.18
Bumps [proc-macro2](https://github.com/alexcrichton/proc-macro2) from 1.0.17 to 1.0.18.
- [Release notes](https://github.com/alexcrichton/proc-macro2/releases)
- [Commits](https://github.com/alexcrichton/proc-macro2/compare/1.0.17...1.0.18)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-01 08:51:26 +01:00
dependabot-preview[bot]
a1b9131bc6 build(deps): bump syn from 1.0.29 to 1.0.30
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.29 to 1.0.30.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.29...1.0.30)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-01 08:51:12 +01:00
dependabot-preview[bot]
2571b27966 build(deps): bump vcpkg from 0.2.8 to 0.2.9
Bumps [vcpkg](https://github.com/mcgoo/vcpkg-rs) from 0.2.8 to 0.2.9.
- [Release notes](https://github.com/mcgoo/vcpkg-rs/releases)
- [Changelog](https://github.com/mcgoo/vcpkg-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mcgoo/vcpkg-rs/compare/vcpkg-rs-0.2.8...vcpkg-rs-0.2.9)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-01 08:50:55 +01:00
dependabot-preview[bot]
57f477efa1 build(deps): bump syn from 1.0.28 to 1.0.29
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.28 to 1.0.29.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.28...1.0.29)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-31 17:46:29 +01:00
dependabot-preview[bot]
8a08ea466a build(deps): bump serde_derive from 1.0.110 to 1.0.111
Bumps [serde_derive](https://github.com/serde-rs/serde) from 1.0.110 to 1.0.111.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.110...v1.0.111)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-31 17:46:02 +01:00
dependabot-preview[bot]
b8ae30d454 build(deps): bump serde from 1.0.110 to 1.0.111
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.110 to 1.0.111.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.110...v1.0.111)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-31 17:45:42 +01:00
dependabot-preview[bot]
0a0fb2468a build(deps): bump syn from 1.0.27 to 1.0.28
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.27 to 1.0.28.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.27...1.0.28)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-30 00:51:13 +02:00
dependabot-preview[bot]
bc2921b22c build(deps): bump regex from 1.3.8 to 1.3.9
Bumps [regex](https://github.com/rust-lang/regex) from 1.3.8 to 1.3.9.
- [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/1.3.8...1.3.9)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-29 08:17:07 +01:00
dependabot-preview[bot]
917ad53024 build(deps): bump regex from 1.3.7 to 1.3.8
Bumps [regex](https://github.com/rust-lang/regex) from 1.3.7 to 1.3.8.
- [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-1.3.7...1.3.8)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-28 17:07:31 +01:00
dependabot-preview[bot]
aac87196d6 build(deps): bump vm-memory from 0.2.0 to 0.2.1
Bumps [vm-memory](https://github.com/rust-vmm/vm-memory) from 0.2.0 to 0.2.1.
- [Release notes](https://github.com/rust-vmm/vm-memory/releases)
- [Changelog](https://github.com/rust-vmm/vm-memory/blob/v0.2.1/CHANGELOG.md)
- [Commits](https://github.com/rust-vmm/vm-memory/compare/v0.2.0...v0.2.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-28 17:06:48 +01:00
Rob Bradford
4c2e6054c0 build: Update to latest version of container
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-27 19:32:12 +02:00
Rob Bradford
c471ae944c Dockerfile: Update to latest Rust toolchain: 1.43.0
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-27 19:32:12 +02:00
Rob Bradford
c31ad72ee9 build: Address issues found by 1.43.0 clippy
These are mostly due to use of "bare use" statements and unnecessary vector
creation.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-27 19:32:12 +02:00
Bo Chen
fbd1a6c5f1 vmm: api: Return complete error responses in handle_http_request()
Instead of responding only headers with error code, we now return
complete error responses to HTTP requests with errors (e.g. undefined
endpoints and InternalSeverError).

Fixes: #472

Signed-off-by: Bo Chen <chen.bo@intel.com>
2020-05-27 18:29:52 +01:00
Rob Bradford
0728bece0c vmm: seccomp: Ensure that umask() can be reprogrammed
When doing self spawning the child will attempt to set the umask() again. Let
it through the seccomp rules so long as it the safe mask again.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-27 16:46:51 +01:00
Rob Bradford
3497eeff49 main: Set the umask to 0077
This ensures that all created filed are only read/write for the current user.

Fixes: #1240

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-27 16:46:51 +01:00
dependabot-preview[bot]
c1d15de7fc build(deps): bump syn from 1.0.25 to 1.0.27
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.25 to 1.0.27.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.25...1.0.27)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-27 09:02:38 +02:00
dependabot-preview[bot]
a4bb96d45c build(deps): bump libc from 0.2.70 to 0.2.71
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.70 to 0.2.71.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.70...0.2.71)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-27 09:02:13 +02:00
dependabot-preview[bot]
bfd52ad85f build(deps): bump linux-loader from bd01b6d to 1af92d2
Bumps [linux-loader](https://github.com/rust-vmm/linux-loader) from `bd01b6d` to `1af92d2`.
- [Release notes](https://github.com/rust-vmm/linux-loader/releases)
- [Commits](bd01b6dd23...1af92d21ad)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-27 09:01:24 +02:00
Michael Zhao
8f1f9d9e6b devices: Implement InterruptController on AArch64
This commit only implements the InterruptController crate on AArch64.
The device specific part for GIC is to be added.

Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-05-26 11:09:19 +02:00
Michael Zhao
b32d3025f3 devices: Refactor IOAPIC to cover other architectures
IOAPIC, a X86 specific interrupt controller, is referenced by device
manager and CPU manager. To work with more architectures, a common
type for all architectures is needed.
This commit introduces trait InterruptController to provide architecture
agnostic functions. Device manager and CPU manager can use it without
caring what the underlying device is.

Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-05-26 11:09:19 +02:00
dependabot-preview[bot]
d588418053 build(deps): bump syn from 1.0.24 to 1.0.25
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.24 to 1.0.25.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.24...1.0.25)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-26 06:36:22 +02:00
dependabot-preview[bot]
83c18de56a build(deps): bump proc-macro-hack from 0.5.15 to 0.5.16
Bumps [proc-macro-hack](https://github.com/dtolnay/proc-macro-hack) from 0.5.15 to 0.5.16.
- [Release notes](https://github.com/dtolnay/proc-macro-hack/releases)
- [Commits](https://github.com/dtolnay/proc-macro-hack/compare/0.5.15...0.5.16)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-25 10:14:07 +02:00
dependabot-preview[bot]
7708b95ec7 build(deps): bump syn from 1.0.23 to 1.0.24
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.23 to 1.0.24.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.23...1.0.24)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-25 07:18:56 +02:00
dependabot-preview[bot]
749f2f0314 build(deps): bump proc-macro2 from 1.0.15 to 1.0.17
Bumps [proc-macro2](https://github.com/alexcrichton/proc-macro2) from 1.0.15 to 1.0.17.
- [Release notes](https://github.com/alexcrichton/proc-macro2/releases)
- [Commits](https://github.com/alexcrichton/proc-macro2/compare/1.0.15...1.0.17)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-25 07:17:38 +02:00
dependabot-preview[bot]
c98d6fd035 build(deps): bump openssl-sys from 0.9.56 to 0.9.57
Bumps [openssl-sys](https://github.com/sfackler/rust-openssl) from 0.9.56 to 0.9.57.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-sys-v0.9.56...openssl-sys-v0.9.57)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-25 07:17:03 +02:00
dependabot-preview[bot]
a9ca493b5d build(deps): bump proc-macro2 from 1.0.14 to 1.0.15
Bumps [proc-macro2](https://github.com/alexcrichton/proc-macro2) from 1.0.14 to 1.0.15.
- [Release notes](https://github.com/alexcrichton/proc-macro2/releases)
- [Commits](https://github.com/alexcrichton/proc-macro2/compare/1.0.14...1.0.15)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-22 22:23:31 +01:00
dependabot-preview[bot]
974c71389c build(deps): bump thiserror from 1.0.18 to 1.0.19
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.18 to 1.0.19.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.18...1.0.19)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-22 17:26:44 +01:00
dependabot-preview[bot]
321c479b03 build(deps): bump proc-macro2 from 1.0.13 to 1.0.14
Bumps [proc-macro2](https://github.com/alexcrichton/proc-macro2) from 1.0.13 to 1.0.14.
- [Release notes](https://github.com/alexcrichton/proc-macro2/releases)
- [Commits](https://github.com/alexcrichton/proc-macro2/compare/1.0.13...1.0.14)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-22 17:26:39 +01:00
Michael Zhao
4f5c8be38e build: Added a workflow to cross-build targetting AArch64
The result of the workflow can be seen in Checks tab of a PR.
Two targets have been added:
- stable aarch64-unknown-linux-gnu
- stable aarch64-unknown-linux-musl

Note: a temporary step was added before building. We used "sed" command
to remove "with-serde" feature of kvm-bindings in vmm/Cargo.toml.
This step should be removed in future when kvm-bindings is ready.

Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-05-21 11:56:26 +01:00
Michael Zhao
1befae872d build: Fixed build errors and warnings on AArch64
This is a preparing commit to build and test CH on AArch64. All building
issues were fixed, but no functionality was introduced.
For X86, the logic of code was not changed at all.
For ARM, the architecture specific part is still empty. And we applied
some tricks to workaround lint warnings. But such code will be replaced
later by other commits with real functionality.

Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-05-21 11:56:26 +01:00
Michael Zhao
0090ec2dda build: Updated development utilities for AArch64
Updated Dockerfile to work with multiple architectures.
Updated dev_cli.sh to:
1. Build container image before AArch64 image is ready in public.
2. Adjust default feature collection on AArch64.
3. Workaround a build problem with musl on AArch64.

Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-05-21 11:56:26 +01:00
Rob Bradford
af8292b623 vmm, config, vhost_user_blk: remove "wce" parameter
This config option provided very little value and instead we now enable
this feature (which then lets the guest control the cache mode)
unconditionally.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-21 08:40:43 +02:00
Rob Bradford
9101bdd7a9 vm-virtio: block: Ensure backing file consistency
Correctly implement the virtio specification by setting the writeback
field on the request based on the algorithm in the spec.

TEST=Boot with hypervisor-firmware with CH in verbose mode. See info
level messages saying cache mode is writethrough in firmware (no support
for flush or WCE). Once in the Linux kernel see messages that mode is
writeback.

Fixes: #1216
Fixes: #680

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-21 08:40:43 +02:00
Rob Bradford
dc66eee8f0 vhost_user_block: Ensure backing file consistency
Correctly implement the virtio specification by setting the writeback
field on the request based on the algorithm in the spec.

TEST=Boot with hypervisor-firmware with CH in verbose mode. See info
level messages saying cache mode is writethrough in firmware (no support
for flush or WCE). Once in the Linux kernel see messages that mode is
writeback.

Fixes: #1216

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-21 08:40:43 +02:00
Rob Bradford
10db2131bd vm-virtio: block: Add "writeback" control to Request
When this is set to false the write needs to be followed by a flush on
the underlying disk (leading to a fsync()).

The default behaviour is not changed with this change.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-21 08:40:43 +02:00
Rob Bradford
b94d9a30d3 vhost_user_backend: Allow backends to know features that can be used
Previous to adding a a trait method to inform the backends of the acked
features backends can use features than the guest has not enabled which
could lead to unpredictable results.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-21 08:40:43 +02:00
Rob Bradford
9d88ba7afb vhost_user_block: Use VirtioBlockConfig from vm-virtio
Use the same definition of the struct as vm-virtio.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-21 08:40:43 +02:00
Rob Bradford
1fac263263 vm-virtio: Use config name as per spec
The spec calls this field "writeback" which is much clearer than than
"wce".

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-21 08:40:43 +02:00
dependabot-preview[bot]
077a5c367c build(deps): bump syn from 1.0.22 to 1.0.23
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.22 to 1.0.23.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.22...1.0.23)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-21 07:05:20 +01:00
Rob Bradford
a813b57f59 vm-virtio, vhost_user_{fs,block,backend}: Move EVENT_IDX handling
Move the method that is used to decide whether the guest should be
signalled into the Queue implementation from vm-virtio. This removes
duplicated code between vhost_user_backend and the vm-virtio block
implementation.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-20 12:56:25 +02:00
Rob Bradford
8ae7a38da5 build: Use same virtio-bindings version
Consistently use the crates.io 0.1.0 version based on Linux 5.0.0

Fixes: #1192

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-20 12:55:59 +02:00
Rob Bradford
3947809c36 vm-virtio: block: Ensure that VIRTIO_BLK_T_FLUSH requests actually sync
The implementation of this virtio block (and vhost-user block) command
called a function that was a no-op on Linux. Use the same function as
virtio-pmem to ensure that data is not lost when the guest asks for it
to be flused to disk.

Fixes: #399

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-20 12:54:10 +02:00
dependabot-preview[bot]
ca6edafbfe build(deps): bump cc from 1.0.53 to 1.0.54
Bumps [cc](https://github.com/alexcrichton/cc-rs) from 1.0.53 to 1.0.54.
- [Release notes](https://github.com/alexcrichton/cc-rs/releases)
- [Commits](https://github.com/alexcrichton/cc-rs/compare/1.0.53...1.0.54)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-20 07:42:00 +02:00
Sebastien Boeuf
a7f236b896 ci: Extend snapshot/restore to validate virtio-vsock
Now that snapshot/restore support has been enabled for virtio-vsock, the
corresponding integration test is expanded with some validation that
virtio-vsock supports to be snapshot and restored.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-19 18:52:08 +02:00
Sebastien Boeuf
f442c62bc5 vm-virtio: Implement Snapshottable trait for Vsock
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-19 18:52:08 +02:00
Sebastien Boeuf
f975998875 ci: Extend snapshot/restore test with virtio-iommu
When compiled with pci feature, the integration test now validates that
/dev/vdb can be correctly read while being placed behing a virtual
IOMMU.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-19 09:03:41 +01:00
Sebastien Boeuf
646d33fea3 vm-virtio: Set queue fields explicitely during restore
For both virtio-mmio and virtio-pci transport layers, we were setting
every field from the saved snapshot during a restore. This is a problem
when we don't want to override specific fields such as iommu_mapping_cb
because the saved snapshot doesn't contain the appropriate information.

That's why this commit sets only the appropriate field from the saved
snapshot during a restore.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-19 09:03:41 +01:00
Sebastien Boeuf
02cbea546d vm-virtio: Implement Snapshottable trait for Iommu
Provide implementation for both snapshot() and restore() methods from
the Snapshottable trait, so that we can snapshot and restore a VM with
devices attached to a virtual IOMMU.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-19 09:03:41 +01:00
dependabot-preview[bot]
4f89cb050a build(deps): bump linux-loader from 43d1c51 to bd01b6d
Bumps [linux-loader](https://github.com/rust-vmm/linux-loader) from `43d1c51` to `bd01b6d`.
- [Release notes](https://github.com/rust-vmm/linux-loader/releases)
- [Commits](43d1c51c32...bd01b6dd23)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-19 09:49:37 +02:00
dependabot-preview[bot]
14db7b0ac0 build(deps): bump addr2line from 0.12.0 to 0.12.1
Bumps [addr2line](https://github.com/gimli-rs/addr2line) from 0.12.0 to 0.12.1.
- [Release notes](https://github.com/gimli-rs/addr2line/releases)
- [Changelog](https://github.com/gimli-rs/addr2line/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gimli-rs/addr2line/compare/0.12.0...0.12.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-19 07:48:46 +00:00
Sebastien Boeuf
9f2eddd9d8 ci: Fix test_serial_off
The integration test validating that --serial off works correctly was
not properly written as it was using the FW, which by default would use
the kernel command line found in the EFI partition. Unfortunately, this
kernel command line was including "console=ttyS0", which causes the
kernel to try to write to the serial port, even if there's no serial
port being emulated.

The problem is, when no emulation of the serial port is provided, the
default value returned on 0x3f8 is 0, which makes the guest kernel think
that some data needs to be read.

The only way to avoid all this is by ensuring we can control the kernel
command line by removing any occurence of "console=ttyS0" from it.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-19 09:22:09 +02:00
Bo Chen
7c3e19c65a vhost_user_backend, vmm: Close leaked file descriptors
Explicit call to 'close()' is required on file descriptors allocated
from 'epoll::create()', which is missing for the 'EpollContext' and
'VringWorker'. This patch enforces to close the file descriptors by
reusing the Drop trait of the 'File' struct.

Signed-off-by: Bo Chen <chen.bo@intel.com>
2020-05-19 09:22:09 +02:00
Bo Chen
35782bd9f8 vm-virtio: Close file descriptors created by epoll::create()
This patch fixes file descriptor leak related to epoll::create() from
various virtio devices.

Fixes: #1124

Signed-off-by: Bo Chen <chen.bo@intel.com>
2020-05-19 09:22:09 +02:00
Rob Bradford
039accc139 vhost_user_net, vm-virtio: Interrupt guest when TX queue is updated
According to the virtio spec the guest should always be interrupted when
"used" descriptors are returned from the device to the driver. However
this was not the case for the TX queue in either the virtio-net
implementation or the vhost-user-net implementation.

This would have meant that the guest could end up with a reduced TX
throughput as it would not know that the packets had been dispatched via
the VMM.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-18 17:02:53 +02:00
dependabot-preview[bot]
c8a081e459 build(deps): bump pin-project from 0.4.16 to 0.4.17
Bumps [pin-project](https://github.com/taiki-e/pin-project) from 0.4.16 to 0.4.17.
- [Release notes](https://github.com/taiki-e/pin-project/releases)
- [Changelog](https://github.com/taiki-e/pin-project/blob/master/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/pin-project/compare/v0.4.16...v0.4.17)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-18 15:02:50 +02:00
dependabot-preview[bot]
b80a7d01c7 build(deps): bump vmm-sys-util from 0.5.0 to 0.6.0
Bumps [vmm-sys-util](https://github.com/rust-vmm/vmm-sys-util) from 0.5.0 to 0.6.0.
- [Release notes](https://github.com/rust-vmm/vmm-sys-util/releases)
- [Changelog](https://github.com/rust-vmm/vmm-sys-util/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-vmm/vmm-sys-util/compare/v0.5.0...v0.6.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-18 15:02:34 +02:00
Rob Bradford
e6fd6d6360 vhost_user_block: Implement VIRTIO_BLK_F_FLUSH
As the parsing code is reused the flush feature is already implemented
and ready to be used.

Fixes: #1197

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-18 13:11:00 +01:00
dependabot-preview[bot]
95e3eddaf0 build(deps): bump quote from 1.0.5 to 1.0.6
Bumps [quote](https://github.com/dtolnay/quote) from 1.0.5 to 1.0.6.
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.5...1.0.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-18 05:52:50 +00:00
dependabot-preview[bot]
d760010c9e build(deps): bump ppv-lite86 from 0.2.6 to 0.2.8
Bumps [ppv-lite86](https://github.com/cryptocorrosion/cryptocorrosion) from 0.2.6 to 0.2.8.
- [Release notes](https://github.com/cryptocorrosion/cryptocorrosion/releases)
- [Commits](https://github.com/cryptocorrosion/cryptocorrosion/compare/ppv-lite86-0.2.6...ppv-lite86-0.2.8)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-18 06:58:10 +02:00
dependabot-preview[bot]
0cde08a78c build(deps): bump hermit-abi from 0.1.12 to 0.1.13
Bumps [hermit-abi](https://github.com/hermitcore/rusty-hermit) from 0.1.12 to 0.1.13.
- [Release notes](https://github.com/hermitcore/rusty-hermit/releases)
- [Commits](https://github.com/hermitcore/rusty-hermit/compare/hermit-abi-0.1.12...hermit-sys-0.1.13)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-18 06:57:27 +02:00
dependabot-preview[bot]
3adfe3fbb9 build(deps): bump syn from 1.0.21 to 1.0.22
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.21 to 1.0.22.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.21...1.0.22)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-18 06:56:51 +02:00
dependabot-preview[bot]
85aadd1585 build(deps): bump proc-macro2 from 1.0.12 to 1.0.13
Bumps [proc-macro2](https://github.com/alexcrichton/proc-macro2) from 1.0.12 to 1.0.13.
- [Release notes](https://github.com/alexcrichton/proc-macro2/releases)
- [Commits](https://github.com/alexcrichton/proc-macro2/compare/1.0.12...1.0.13)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-18 06:56:23 +02:00
dependabot-preview[bot]
c764c21273 build(deps): bump thiserror from 1.0.17 to 1.0.18
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.17 to 1.0.18.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.17...1.0.18)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-18 06:56:00 +02:00
Rob Bradford
4366dd92ac vm-virtio: block: Add support for VIRTIO_RING_F_EVENT_IDX
Permit the guest to suppress interrupts from the host as an
optimisation.

Fixes: #786

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-15 19:03:41 +02:00
Rob Bradford
5a55fc0737 vhost_user_fs: Fix seccomp filter for musl
Add missing syscall used by the musl build.

TEST=scripts/dev_cli.sh tests --libc musl --integration -- vhost_user_fs_daemon

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-15 16:33:48 +02:00
Rob Bradford
391508f054 tests: Add tests checking for host MAC address setting
Extend the set of tests we have for virtio-net and vhost-user-net to
check for host MAC address setting.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-15 11:45:09 +01:00
Rob Bradford
1b8b5ac179 vhost-user_net, vm-virtio, vmm: Permit host MAC address setting
Add a new "host_mac" parameter to "--net" and "--net-backend" and use
this to set the MAC address on the tap interface. If no address is given
one is randomly assigned and is stored in the config.

Support for vhost-user-net self spawning was also included.

Fixes: #1177

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-15 11:45:09 +01:00
Rob Bradford
11049401ce vmm: seccomp: Add ioctl() commands interface hardware address
This is necessary to support setting the MAC address on the tap
interface on the host.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-15 11:45:09 +01:00
Rob Bradford
59e1361f5e net_util: tap: Add support for setting tap MAC address
Do this by reading the HW address information and then modifying the
HW address to match the desired address. Preserving the rest of the
state including the address type.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-15 11:45:09 +01:00
Sebastien Boeuf
68fc432978 vmm: Update seccomp filters with clock_nanosleep
The clock_nanosleep system call needs to be whitelisted since the commit
12e00c0f45 introduced the use of a sleep()
function. Without this patch, we can see an error when the VM is paused
or killed.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-15 12:34:53 +02:00
dependabot-preview[bot]
badf826196 build(deps): bump anyhow from 1.0.30 to 1.0.31
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.30 to 1.0.31.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.30...1.0.31)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-15 09:24:01 +02:00
dependabot-preview[bot]
7b10f7320d build(deps): bump cc from 1.0.52 to 1.0.53
Bumps [cc](https://github.com/alexcrichton/cc-rs) from 1.0.52 to 1.0.53.
- [Release notes](https://github.com/alexcrichton/cc-rs/releases)
- [Commits](https://github.com/alexcrichton/cc-rs/compare/1.0.52...1.0.53)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-14 17:21:52 +00:00
Dr. David Alan Gilbert
4120a7dee9 vhost_user_fs: Add seccomp
Implement seccomp; we use one filter for all threads.
The syscall list comes from the C daemon with syscalls added
as I hit them.

The default behaviour is to kill the process, this normally gets
audit logged.

--seccomp none  disables seccomp
          log   Just logs violations but doesn't stop it
          trap  causes a signal to be be sent that can be trapped.

If you suspect you're hitting a seccomp action then you can
check the audit log;  you could also switch to running with 'log'
to collect a bunch of calls to report.
To see where the syscalls are coming from use 'trap' with a debugger
or coredump to backtrace it.

This can be improved for some syscalls to restrict the parameters
to some syscalls to make them more restrictive.

Signed-off-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
2020-05-14 18:56:19 +02:00
Rob Bradford
6aa29bdb24 vmm: api: Use a common handler for data actions too
Like the actions that don't take data such as "pause" or "resume" use a
common handler implementation to remove duplicated code for handling
simple endpoints like the hotplug ones.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-14 16:55:51 +01:00
Rob Bradford
0fe223f00e vmm: api: Extend VmAction to reduce code duplication
Many of the API requests take a similar form with a single data item
(i.e. config for a device hotplug) expand the VmAction enum to handle
those actions and a single function to dispatch those API events.

For now port the existing helper functions to use this new API. In the
future the HTTP layer can create the VmAction directly avoiding the
extra layer of indirection.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-14 16:55:51 +01:00
Rob Bradford
6ec605a7fb vmm: api: Refactor generic action handler
Rather than save the save a function pointer and use that instead the
underlying action. This is useful for two reasons:

1. We can ensure that we generate HttpErrors in the same way as the
   other endpoints where API error variant should be determined by the
   request being made not the underlying error.
2. It can be extended to handle other generic actions where the function
   prototype differs slightly.

As result of this refactoring it was found that the "vm.delete" endpoint
was not connected so address that issue.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-14 16:55:51 +01:00
Rob Bradford
c652625beb vmm: api: Add a default implementation for simple PUT requests
Extend the EndpointHandler trait to include automatic support for
handling PUT requests. This will allow the removal of lots of duplicated
code in the following commit from the API handling code.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-14 16:55:51 +01:00
Rob Bradford
a3e8bea03c vmm: api: Move HttpError enum to http module
Minor rearrangement of code to make it easier to implement refactoring.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-14 16:55:51 +01:00
Sergio Lopez
6aab0a5458 vhost_user_fs: Implement support for optional sandboxing
Implement support for setting up a sandbox for running the
service. The technique for this has been borrowed from virtiofsd, and
consists on switching to new PID, mount and network namespaces, and
then switching root to the directory to be shared.

Future patches will implement additional hardening features like
dropping capabilities and seccomp filters.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2020-05-14 17:16:23 +02:00
Sergio Lopez
c4bf383fd7 vhost_user_*: Create a vhost::Listener in advance
Changes is vhost crate require VhostUserDaemon users to create and
provide a vhost::Listener in advance. This allows us to adopt
sandboxing strategies in the future, by being able to create the UNIX
socket before switching to a restricted namespace.

Update also the reference to vhost crate in Cargo.lock to point to the
latest commit from the dragonball branch.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2020-05-14 17:16:23 +02:00
Sergio Lopez
fa844865a5 vhost_user_fs: Allow callers to provide a fd for /proc/self/fd
Allow callers to provide a file descriptor for /proc/self/fd. This is
useful for sandboxing, as we may be running in a namespace that
doesn't have access to /proc.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2020-05-14 17:16:23 +02:00
Sergio Lopez
831cff3fee vhost_user_fs: Use a fd for /proc/self/fd instead of /proc
Open a file descriptor to /proc/self/fd instead of /proc. We aren't
using any other entries from that directory, and doing this allows us
to keep working even if /proc is no longer present in our
namespace (useful for sandboxing).

Signed-off-by: Sergio Lopez <slp@redhat.com>
2020-05-14 17:16:23 +02:00
Sebastien Boeuf
ba4ec7fce2 ci: Extend snapshot_restore_test with hotplug
There's a simple way to trigger PCI BAR reprogramming for a given
device, by removing it and then hotplugging it back. The Linux kernel
will simply choose to place the BARs at different location than the ones
chosen by Cloud-Hypervisor. By doing so, and creating the snapshot after
this hotplug operation, we can manage to validate that the resource are
correctly restored for a given virtio-pci device.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-14 11:02:22 +02:00
Sebastien Boeuf
9e165c2c17 ci: Enable snapshot/restore integration test
Now that snapshot/restore feature is stable for both virtio-mmio and
virtio-pci devices, we re-enable the integration test for validating
snapshot/restore does not get broken.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-14 11:02:22 +02:00
dependabot-preview[bot]
c566f1f024 build(deps): bump once_cell from 1.3.1 to 1.4.0
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.3.1 to 1.4.0.
- [Release notes](https://github.com/matklad/once_cell/releases)
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.3.1...v1.4.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-14 09:01:57 +02:00
dependabot-preview[bot]
7ffde29593 build(deps): bump backtrace from 0.3.47 to 0.3.48
Bumps [backtrace](https://github.com/rust-lang/backtrace-rs) from 0.3.47 to 0.3.48.
- [Release notes](https://github.com/rust-lang/backtrace-rs/releases)
- [Commits](https://github.com/rust-lang/backtrace-rs/compare/0.3.47...0.3.48)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-13 23:36:55 +02:00
dependabot-preview[bot]
e9c2dbc866 build(deps): bump anyhow from 1.0.29 to 1.0.30
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.29 to 1.0.30.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.29...1.0.30)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-13 19:56:23 +02:00
Rob Bradford
9ccc7daa83 build, vmm: Update to latest kvm-ioctls
The ch branch has been rebased to incorporate the latest upstream code
requiring a small change to the unit tests.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-13 17:14:49 +02:00
Rob Bradford
80aa0a753d tests: Test unplugging virtio-fs
Now that virtio-fs has unplug support try unplugging and readding the
virtio-fs device.

Fixes: #1050

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-13 09:11:50 +01:00
Rob Bradford
88ec93d075 vmm: config: Add missing "id" from FsConfig parsing
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-13 09:11:50 +01:00
dependabot-preview[bot]
0f89f5eca0 build(deps): bump anyhow from 1.0.28 to 1.0.29
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.28 to 1.0.29.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.28...1.0.29)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-13 09:32:48 +02:00
dependabot-preview[bot]
ab3d374ab7 build(deps): bump syn from 1.0.20 to 1.0.21
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.20 to 1.0.21.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.20...1.0.21)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-13 09:32:38 +02:00
dependabot-preview[bot]
35b8992ede build(deps): bump thiserror from 1.0.16 to 1.0.17
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.16 to 1.0.17.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.16...1.0.17)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-13 08:54:23 +02:00
dependabot-preview[bot]
3415b11d79 build(deps): bump quote from 1.0.4 to 1.0.5
Bumps [quote](https://github.com/dtolnay/quote) from 1.0.4 to 1.0.5.
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.4...1.0.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-13 08:54:12 +02:00
dependabot-preview[bot]
6989bf054c build(deps): bump backtrace from 0.3.46 to 0.3.47
Bumps [backtrace](https://github.com/rust-lang/backtrace-rs) from 0.3.46 to 0.3.47.
- [Release notes](https://github.com/rust-lang/backtrace-rs/releases)
- [Commits](https://github.com/rust-lang/backtrace-rs/compare/0.3.46...0.3.47)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-12 20:27:38 +02:00
dependabot-preview[bot]
2991fd2a48 build(deps): bump libc from 0.2.69 to 0.2.70
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.69 to 0.2.70.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.69...0.2.70)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-12 20:26:43 +02:00
Sebastien Boeuf
c37da600e8 vmm: Update DeviceTree upon PCI BAR reprogramming
By passing a reference of the DeviceTree to the AddressManager, we can
now update the DeviceTree whenever a PCI BAR is reprogrammed. This is
mandatory to maintain the correct resources information related to each
virtio-pci device, which will ensure correct information will be stored
upon VM snapshot.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-12 17:37:31 +01:00
Sebastien Boeuf
d0ae9d7ce6 vmm: Share the DeviceTree across threads
We want to be able to share the same DeviceTree across multiple threads,
particularly to handle the use case where PCI BAR reprogramming might
need to update the tree while from another thread a new device is being
added to the tree.

That's why this patch moves the DeviceTree instance into an Arc<Mutex<>>
so that we can later share a reference of the same mutable tree with the
AddressManager responsible for handling PCI BAR reprogramming.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-12 17:37:31 +01:00
Sebastien Boeuf
5e9d254564 vmm: Store and restore virtio-pci BAR resources
By using the vector of resources provided by the DeviceNode, the device
manager can store the information related to PCI BARs from a virtio-pci
device. Based on this, and upon VM restoration, the device manager can
restore the BARs in the expected location in the guest address space.

One thing to note is that we only need to provide the VirtioPciDevice
with the configuration BAR (BAR 0) since the SHaredMemory BAR info comes
from the virtio device directly.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-12 17:37:31 +01:00
Sebastien Boeuf
02bd50f6ab vm-virtio: Add helper to set the configuration BAR value
From a VirtioPciDevice perspective, there are two types of BARs, either
the virtio configuration BAR or the SHaredMemory BAR.

The SHaredMemory BAR address comes from the virtio device directly as
the memory region had been previously allocated when the virtio device
has been created. So for this BAR, there's nothing to do when restoring
a VM, since the associated virtio device is already restored with the
appropriate resources, hence the BAR will already be at the right
address.

The remaining configuration BAR is different, as we usually get its
address from the SystemAllocator. This means in case we restore a VM,
we must provide this value, bypassing the allocator. This is what this
commit takes care of, by letting the caller set the base address for the
configuration BAR prior to allocating the BARs.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-12 17:37:31 +01:00
Sebastien Boeuf
8a826ae24c vmm: Store and restore virtio-pci device on right PCI slot
Based on the new field "pci_bdf", a virtio-pci device can be restored at
the same place on the PCI bus it was located before the VM snapshot.

This ensures consistent placement on the PCI bus, based on the stored
information related to each device.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-12 17:37:31 +01:00
Sebastien Boeuf
98dac352b8 vmm: Add optional PCI b/d/f to each DeviceNode
We need a way to store the information about where a PCI device was
placed on the PCI bus before the VM was snapshotted. The way to do this
is by adding an extra field to the DeviceNode structure.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-12 17:37:31 +01:00
Sebastien Boeuf
1e0ebb760f pci: Allow specific PCI b/d/f to be reserved
In order to let the PciBus user choose where a device should be placed
on the bus, a new function get_device_id() is introduced. This will be
helpful in the context of snapshot/restore as the caller will be able to
place the PCI devices on the same slot they were placed before the
snapshot was taken.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-12 17:37:31 +01:00
dependabot-preview[bot]
e577b64a58 build(deps): bump syn from 1.0.19 to 1.0.20
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.19 to 1.0.20.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.19...1.0.20)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-12 10:20:15 +02:00
Rob Bradford
36bffff22a tests: Expand the test_large_memory() test to cover lots of vCPUs
Ensure that there is no regression in our support for a large number of
vCPUS.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-11 19:34:34 +01:00
Rob Bradford
b9ba81c30d arch, vmm: Don't build mptable when using ACPI
Use the ACPI feature to control whether to build the mptable. This is
necessary as the mptable and ACPI RSDP table can easily overwrite each
other leading to it failing to boot.

TEST=Compile with default features and see that --cpus boot=48 now
works, try with --no-default-features --features "pci" and observe the
--cpus boot=48 also continues to work.

Fixes: #1132

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-11 19:34:34 +01:00
Rob Bradford
16ac24d8f7 tests: Only test "noacpi" test when we don't build with ACPI
The mmio integration test build currently doesn't use ACPI so piggyback
on that test variation.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-11 19:34:34 +01:00
Rob Bradford
bb8d19bbd6 arch: Check RSDP address does not go past memory
The setup_mptables() call which is not used on ACPI builds has a side
effect of testing whether there was enough RAM which one of the unit
tests was relying on. Add a similar check for the RSDP address.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-11 19:34:34 +01:00
dependabot-preview[bot]
1c44e917f9 build(deps): bump clap from 2.33.0 to 2.33.1
Bumps [clap](https://github.com/clap-rs/clap) from 2.33.0 to 2.33.1.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/v2.33.1/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v2.33.0...v2.33.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-11 20:15:13 +02:00
dependabot-preview[bot]
4cd2eccf2f build(deps): bump signal-hook from 0.1.14 to 0.1.15
Bumps [signal-hook](https://github.com/vorner/signal-hook) from 0.1.14 to 0.1.15.
- [Release notes](https://github.com/vorner/signal-hook/releases)
- [Changelog](https://github.com/vorner/signal-hook/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/signal-hook/compare/v0.1.14...v0.1.15)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-11 20:15:03 +02:00
Sebastien Boeuf
308b790cfc vm-virtio: Implement Snapshottable trait for VirtioPciDevice
This gives Cloud-Hypervisor the possibility to snapshot and restore a
VM running with virtio-pci devices attached to it.

The VirtioPciDevice snapshot contains a vector of sub-snapshots to store
and restore information related to MsixConfig, VirtioPciCommonConfig and
PciConfiguration structures, along with snapshot data related to
VirtioPciDevice itself.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-11 11:38:16 +01:00
Sebastien Boeuf
6d59428641 vm-virtio: Implement Snapshottable trait for VirtioPciCommonConfig
This structure contains all the virtio generic information, and as part
of restoring a VM with virtio-pci devices, it is important to restore
these values to ensure the device's proper functioning.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-11 11:38:16 +01:00
Sebastien Boeuf
e1701f11b1 pci: Implement Snapshottable trait for PciConfiguration
The PCI configuration from each PCI device is modified at runtime as we
can expect the guest OS to write to some PCI capability structure, or
move the BAR to a different location in the guest address space.

For all the reasons why such configuration might differ from the initial
configuration, we must store the registers values to be able to restore
them with the right values whenever a PCI device is being restored.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-11 11:38:16 +01:00
Sebastien Boeuf
376db31107 pci: Implement Snapshottable trait for MsixConfig
In order to restore devices relying on MSI-X, the MsixConfig structure
must be restored with the correct values. Additionally, the KVM routes
must be restored so that interrupts can be delivered through KVM the way
they were configured before the snapshot was taken.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-11 11:38:16 +01:00
Rob Bradford
52ac3779df tests: Remove network interface from test_memory_overhead
Continue to try and track down the instability in the numbers generated
by this test by removing the (unused) network interface. It's possible
we are getting broadcast packets into the tap device and generating a
variable size of allocations.

See: #760

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-11 11:27:19 +02:00
Rob Bradford
b57eeb9628 vhost_user_block: Add "queue_size" to --block-backend
This allows the configuration of the queue size like other backends.

Fixes: #1131

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-11 09:40:40 +02:00
Rob Bradford
5016fcf8d5 vhost_user_block: Use config::OptionParser to simplify block backend parsing
Switch to using the recently added OptionParser in the code that parses
the block backend.

Fixes: #1092

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-11 09:40:40 +02:00
Rob Bradford
592de97fbd vhost_user_net: Use config::OptionParser to simplify net backend parsing
Switch to using the recently added OptionParser in the code that parses
the network backend.

Whilst doing this also update the net-backend syntax to use "sock"
rather than socket.

Fixes: #1092
Partially fixes: #1091

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-11 09:40:40 +02:00
Rob Bradford
f3f398eb44 vhost_user_block: Consolidate the vhost-user-block backend syntax
Rather than repeat syntax for the vhost-user-block backend in multiple
places store it in one place and reference it from the required places.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-11 09:40:40 +02:00
Rob Bradford
3220292d45 vhost_user_net: Consolidate the vhost-user-net backend syntax
Rather than repeat syntax for the vhost-user-net backend in multiple
places store it in one place and reference it from the required places.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-11 09:40:40 +02:00
dependabot-preview[bot]
0d2be3b6d4 build(deps): bump serde from 1.0.107 to 1.0.110
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.107 to 1.0.110.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.107...v1.0.110)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-11 06:43:33 +00:00
dependabot-preview[bot]
9d8754c620 build(deps): bump pin-project from 0.4.13 to 0.4.16
Bumps [pin-project](https://github.com/taiki-e/pin-project) from 0.4.13 to 0.4.16.
- [Release notes](https://github.com/taiki-e/pin-project/releases)
- [Changelog](https://github.com/taiki-e/pin-project/blob/master/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/pin-project/compare/v0.4.13...v0.4.16)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-11 08:01:33 +02:00
dependabot-preview[bot]
9bac13deb3 build(deps): bump serde_json from 1.0.52 to 1.0.53
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.52 to 1.0.53.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.52...v1.0.53)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-11 08:00:54 +02:00
dependabot-preview[bot]
e8d4a13e7f build(deps): bump serde_derive from 1.0.107 to 1.0.110
Bumps [serde_derive](https://github.com/serde-rs/serde) from 1.0.107 to 1.0.110.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.107...v1.0.110)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-11 08:00:37 +02:00
dependabot-preview[bot]
d8f181c576 build(deps): bump futures from 0.3.4 to 0.3.5
Bumps [futures](https://github.com/rust-lang/futures-rs) from 0.3.4 to 0.3.5.
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.4...0.3.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-09 12:31:12 +01:00
dependabot-preview[bot]
1e44ac515f build(deps): bump serde_derive from 1.0.106 to 1.0.107
Bumps [serde_derive](https://github.com/serde-rs/serde) from 1.0.106 to 1.0.107.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.106...v1.0.107)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-09 12:30:55 +01:00
dependabot-preview[bot]
c197bd6f82 build(deps): bump serde from 1.0.106 to 1.0.107
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.106 to 1.0.107.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.106...v1.0.107)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-09 12:30:30 +01:00
Sebastien Boeuf
475040b29e vm-virtio: Correctly reset the virtqueues
Upon a virtio reset, the driver expects that available and used indexes
will be reset to 0. That's why we need to reset these values from the
VMM for any virtio device that might get reset.

This issue was not detected before because the Vec<Queue> maintained
through VirtioPciDevice or MmioDevice was never updated from the virtio
device thread after the device had been actived. For this reason, upon
reset, both available and used indexes were already at the value 0.

The issue arose when trying to reset a device after the VM was restored.
That's because during the restore, each queue is assigned with the right
available and used indexes before it is passed to the device through the
activate function. And that's why upon reset, each queue was still
assigned with these indexes while it should have been reset to 0.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-08 11:31:50 +01:00
Sebastien Boeuf
d809f2fe09 vm-virtio: Add virtio reset() support to MmioDevice
All our virtio devices support to be reset, but the virtio-mmio
transport layer was not implemented for it. This patch fixes this
lack of support.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-08 11:31:16 +01:00
Rob Bradford
0d720cc3d8 bin: ch-remote: Ensure ch-remote supports syntax it advertises
The ch-remote usage says:

OPTIONS:
        --api-socket <api-socket>    HTTP API socket path (UNIX domain socket).

However it doesn't seem to actually accept that syntax, instead
requiring "--api-socket=". This may be a clap bug however it is resolved
by setting the number of arguments requires to exactly one. Which is
also the actual correct number.

Fixes: #1117
Fixes: #1116

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-08 09:54:50 +01:00
dependabot-preview[bot]
74d88c4c8a build(deps): bump openssl-sys from 0.9.55 to 0.9.56
Bumps [openssl-sys](https://github.com/sfackler/rust-openssl) from 0.9.55 to 0.9.56.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-sys-v0.9.55...openssl-sys-v0.9.56)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-08 10:13:33 +02:00
Rob Bradford
9adc32a043 tests: Print out details for smaps in test_memory_overhead
To aid the debugging of why the test_memory_overhead sometimes fails
print a details of all the named regions that it uses in its
calculation.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-07 17:53:47 +02:00
Rob Bradford
250f825f58 tests: Check that requesting tap name for virtio-net succeeds
When requesting a specific tap name with virtio-net check that that tap
device is created.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-07 13:41:00 +02:00
Rob Bradford
006da0405e tests: Check tap name provided is used for vhost_user_net tests
If a preferred tap name is given check that it is created.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-07 13:41:00 +02:00
Rob Bradford
54b3329f0c tests: Add tests that use (non-existing) named tap
The current tests support giving a tap name but they only test with tap
interfaces that already exist.

Fixes: #871

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-07 13:41:00 +02:00
Rob Bradford
6fde2d18a6 build: Strip the binaries before using/releasing them
Stripping the release build for glibc shrinks the size considerably:

$ du -h target/release/cloud-hypervisor
8.5M    target/release/cloud-hypervisor
$ strip target/release/cloud-hypervisor
$ du -h target/release/cloud-hypervisor
5.2M    target/release/cloud-hypervisor

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-07 12:31:01 +01:00
dependabot-preview[bot]
a4d23c3cf3 build(deps): bump syn from 1.0.18 to 1.0.19
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.18 to 1.0.19.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.18...1.0.19)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-07 08:12:17 +00:00
Rob Bradford
12e00c0f45 vmm: cpu: Retry sending signals if necessary
To avoid a race condition where the signal might "miss" the KVM_RUN
ioctl() instead reapeatedly try sending a signal until the vCPU run is
interrupted (as indicated by setting a new per vCPU atomic.)

It important to also clear this atomic when coming out of a paused
state.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-07 09:00:14 +02:00
Rob Bradford
31bde4f5da vmm: Unpark the DeviceManager threads in shutdown
To ensure that the DeviceManager threads (such as those used for virtio
devices) are cleaned up it is necessary to unpark them so that they get
cleanly terminated as part of the shutdown.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-07 09:00:14 +02:00
Rob Bradford
801e72ac6d vmm: cpu: Unpause vCPU threads
After setting the kill signal flag for the vCPU thread release the pause
flag and unpark the threads. This ensures that that the vCPU thread will
wake up and check the kill signal flag if the VM is paused.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-07 09:00:14 +02:00
Rob Bradford
91a4a2581e vmm: cpu: When coming out of the pause event check for a kill signal
Rather than immediately entering the vCPU run() code check if the kill
signal is set. This allows paused VMs to be shutdown.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-07 09:00:14 +02:00
Rob Bradford
cd60de8f7f Revert "vmm: vm: Unpark the threads before shutdown when the current state is paused"
This reverts commit e1a07ce3c4.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-07 09:00:14 +02:00
dependabot-preview[bot]
797cd13dcc build(deps): bump vec_map from 0.8.1 to 0.8.2
Bumps [vec_map](https://github.com/contain-rs/vec-map) from 0.8.1 to 0.8.2.
- [Release notes](https://github.com/contain-rs/vec-map/releases)
- [Commits](https://github.com/contain-rs/vec-map/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-06 15:25:15 +01:00
Sebastien Boeuf
f6a71bec36 vmm: Add unit tests for DeviceTree
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-05 16:08:42 +02:00
Sebastien Boeuf
64e01684f9 vmm: Create new module device_tree
This module will be dedicated to DeviceNode and DeviceTree definitions
along with some dedicated unit tests.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-05 16:08:42 +02:00
Sebastien Boeuf
3b77be903d vmm: Add device_node!() macro to improve code readability
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-05 16:08:42 +02:00
Sebastien Boeuf
83ec716ec4 vmm: Create breadth-first search iterator for the DeviceTree
This iterator will let the VMM enumerate the resources associated
with the DeviceManager, allowing for introspection.

Moreover, by implementing a double ended iterator, we can get the
hierarchy from the leaves to the root of the tree, which is very
helpful in the context of restoring the devices in the right order.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-05 16:08:42 +02:00
Sebastien Boeuf
b91ab1e3a5 vmm: Remove the list of migratable devices
Now that the device tree fully replaced the need for a dedicated list of
migratable devices, this commit cleans up the codebase by removing it
from the DeviceManager.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-05 16:08:42 +02:00
Sebastien Boeuf
1be7037229 vmm: Don't use migratable_devices for restore
This commit switches from migratable_devices to device_tree in order to
restore devices exclusively based on the device tree.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-05 16:08:42 +02:00
Sebastien Boeuf
bc6084390f vmm: Add migratable field to the DeviceNode
This commit adds an extra field to the DeviceNode so that the structure
can hold a Migratable device. The long term plan is to be able to remove
the dedicated table of migratable devices, but instead rely only on the
device tree.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-05 16:08:42 +02:00
Sebastien Boeuf
7fec020f53 vmm: Create a dedicated DeviceTree structure
In order to hide the complexity chosen for the device tree stored in
the DeviceManager, we introduce a new DeviceTree structure.

For now, this structure is a simple passthrough of a HashMap, but it can
be extended to handle some DeviceTree specific operations.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-05 16:08:42 +02:00
Sebastien Boeuf
14b379dec5 vmm: Add an identifier field to DeviceNode structure
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-05 16:08:42 +02:00
Sebastien Boeuf
0805d458c4 vmm: Add support for multiple children per DeviceNode
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-05 16:08:42 +02:00
Sebastien Boeuf
daaeba5142 vmm: Change Node into DeviceNode
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-05 16:08:42 +02:00
Sebastien Boeuf
5c7df03efe vmm: Store and restore virtio-pmem resources
This device has a dedicated memory region in the guest address space,
which means in case of snapshot/restore, it must be restored in the
exact same location it was during the snapshot.

That's through the resources that we can describe the location of this
extra memory region, allowing the device for correct restoring.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-05 16:08:42 +02:00
Sebastien Boeuf
2e6895d911 vmm: Store and restore virtio-fs resources
This device has a dedicated memory region in the guest address space,
which means in case of snapshot/restore, it must be restored in the
exact same location it was during the snapshot.

That's through the resources that we can describe the location of this
extra memory region, allowing the device for correct restoring.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-05 16:08:42 +02:00
Sebastien Boeuf
987f82152e vmm: Store and restore virtio-mmio resources
Based on the device tree, retrieve the resources associated with a
virtio-mmio device to restore it at the right location in guest address
space. Also, the IRQ number is correctly restored.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-05 16:08:42 +02:00
Sebastien Boeuf
9cb1e1cc6b vmm: Perform MMIO allocation from virtio-mmio device creation
Instead of splitting the MMIO allocation and the device creation into
separate functions for virtio-mmio devices, it's is easier to move
everything into the same function as we'll be able to gather resources
in the same place for the same device.

These resources will be stored in the device tree in a follow up patch.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-05 16:08:42 +02:00
Sebastien Boeuf
adf297066d vmm: Create devices in different path if restoring the VM
In case the VM is created from scratch, the devices should be created
after the DeviceManager has been created. But this should not affect the
restore codepath, as in this case the devices should be created as part
of the restore() function.

It's necessary to perform this differentiation as the restore must go
through the following steps:
- Create the DeviceManager
- Restore the DeviceManager with the right state
- Create the devices based on the restored DeviceManager's device tree
- Restore each device based on the restored DeviceManager's device tree

That's why this patch leverages the recent split of the DeviceManager's
creation to achieve what's needed.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-05 16:08:42 +02:00
Sebastien Boeuf
d39f91de02 vmm: Reorganize DeviceManager creation
This commit performs the split of the DeviceManager's creation into two
separate functions by moving anything related to device's creation after
the DeviceManager structure has been initialized.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-05 16:08:42 +02:00
Sebastien Boeuf
89c2a5868c vmm: Restore devices following the device tree
Based on the device tree, we now ensure the restore can be done in the
right order, as it will respect the dependencies between nodes.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-05 16:08:42 +02:00
Sebastien Boeuf
52c80cfcf5 vmm: Snapshot and restore DeviceManager state
The DeviceManager itself must be snapshotted in order to store the
information regarding the devices associated with it, which effectively
means we need to store the device tree.

The mechanics to snapshot and restore the DeviceManagerState are added
to the existing snapshot() and restore() implementations.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-05 16:08:42 +02:00
Sebastien Boeuf
5b408eec66 vmm: Create a device tree
The DeviceManager now creates a tree of devices in order to store the
resources associated with each device, but also to track dependencies
between devices.

This is a key part for proper introspection, but also to support
snapshot and restore correctly.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-05 16:08:42 +02:00
Sebastien Boeuf
a6fde0bb1c vm-device: Define a Resource
Based on rust-vmm code, we copy the Resource structure as it will be
used to define the set of resources associated with a device.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-05-05 16:08:42 +02:00
Rob Bradford
b8841d7a82 tests: Validate vsock functionality works across a reboot
Reboot the VM and check that the vsock functionality is preserved.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-05 13:01:38 +02:00
Rob Bradford
fec97e0586 vm-virtio, vmm: Delete unix socket on shutdown
It's not possible to call UnixListener::Bind() on an existing file so
unlink the created socket when shutting down the Vsock device.

This will allow the VM to be rebooted with a vsock device.

Fixes: #1083

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-05 13:01:38 +02:00
Rob Bradford
5109f914eb vmm: config: Reject attempts to use VFIO or IOMMU without PCI
Generate an error during validation if an attempt it made to place a
device behind an IOMMU or using a VFIO device when not using PCI.

Fixes: #751

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-05 11:20:52 +01:00
Rob Bradford
cb220ae184 tests: Add some debugging to test_memory_overhead
This test is flaky. Add some debugging to identify what the bad value
is.

See: #760

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-05-05 11:35:12 +02:00
dependabot-preview[bot]
eb3d9d15bf build(deps): bump ssh2 from 0.8.0 to 0.8.1
Bumps [ssh2](https://github.com/alexcrichton/ssh2-rs) from 0.8.0 to 0.8.1.
- [Release notes](https://github.com/alexcrichton/ssh2-rs/releases)
- [Commits](https://github.com/alexcrichton/ssh2-rs/compare/0.8.0...0.8.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-04 08:20:01 +00:00
dependabot-preview[bot]
59b730342d build(deps): bump failure from 0.1.7 to 0.1.8
Bumps [failure](https://github.com/rust-lang-nursery/failure) from 0.1.7 to 0.1.8.
- [Release notes](https://github.com/rust-lang-nursery/failure/releases)
- [Changelog](https://github.com/rust-lang-nursery/failure/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-lang-nursery/failure/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-04 07:26:47 +00:00
dependabot-preview[bot]
dd0791d7c8 build(deps): bump pnet from 0.25.0 to 0.26.0
Bumps [pnet](https://github.com/libpnet/libpnet) from 0.25.0 to 0.26.0.
- [Release notes](https://github.com/libpnet/libpnet/releases)
- [Commits](https://github.com/libpnet/libpnet/compare/v0.25.0...v0.26.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-04 07:26:30 +00:00
dependabot-preview[bot]
7660a104d9 build(deps): bump failure_derive from 0.1.7 to 0.1.8
Bumps [failure_derive](https://github.com/rust-lang-nursery/failure) from 0.1.7 to 0.1.8.
- [Release notes](https://github.com/rust-lang-nursery/failure/releases)
- [Changelog](https://github.com/rust-lang-nursery/failure/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-lang-nursery/failure/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-04 09:00:56 +02:00
Hui Zhu
327d67fadf virtio-mem: Return reize error in MemEpollHandler.run
Return resize error in MemEpollHandler.run.

Fixes: #1081

Signed-off-by: Hui Zhu <teawater@antfin.com>
2020-05-03 10:21:49 +01:00
dependabot-preview[bot]
bc318b64e9 build(deps): bump proc-macro2 from 1.0.10 to 1.0.12
Bumps [proc-macro2](https://github.com/alexcrichton/proc-macro2) from 1.0.10 to 1.0.12.
- [Release notes](https://github.com/alexcrichton/proc-macro2/releases)
- [Commits](https://github.com/alexcrichton/proc-macro2/compare/1.0.10...1.0.12)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-02 19:01:45 +01:00
dependabot-preview[bot]
5571c6af2d build(deps): bump signal-hook from 0.1.13 to 0.1.14
Bumps [signal-hook](https://github.com/vorner/signal-hook) from 0.1.13 to 0.1.14.
- [Release notes](https://github.com/vorner/signal-hook/releases)
- [Changelog](https://github.com/vorner/signal-hook/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/signal-hook/compare/v0.1.13...v0.1.14)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-02 19:01:33 +01:00
dependabot-preview[bot]
af3d080226 build(deps): bump pnet_macros from 0.25.0 to 0.26.0
Bumps [pnet_macros](https://github.com/libpnet/libpnet) from 0.25.0 to 0.26.0.
- [Release notes](https://github.com/libpnet/libpnet/releases)
- [Commits](https://github.com/libpnet/libpnet/compare/v0.25.0...v0.26.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-02 19:01:13 +01:00
dependabot-preview[bot]
678855e858 build(deps): bump term_size from 0.3.1 to 0.3.2
Bumps [term_size](https://github.com/kbknapp/term_size-rs) from 0.3.1 to 0.3.2.
- [Release notes](https://github.com/kbknapp/term_size-rs/releases)
- [Changelog](https://github.com/clap-rs/term_size-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kbknapp/term_size-rs/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-02 19:01:05 +01:00
dependabot-preview[bot]
2a16ce7e41 build(deps): bump quote from 1.0.3 to 1.0.4
Bumps [quote](https://github.com/dtolnay/quote) from 1.0.3 to 1.0.4.
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.3...1.0.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-30 23:03:47 +02:00
dependabot-preview[bot]
99e3a15014 build(deps): bump backtrace-sys from 0.1.36 to 0.1.37
Bumps [backtrace-sys](https://github.com/alexcrichton/backtrace-rs) from 0.1.36 to 0.1.37.
- [Release notes](https://github.com/alexcrichton/backtrace-rs/releases)
- [Commits](https://github.com/alexcrichton/backtrace-rs/compare/backtrace-sys-0.1.36...backtrace-sys-0.1.37)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-30 17:22:02 +01:00
Rob Bradford
96be82296f release: Release v0.7.0
Expand release notes and bump Cargo.toml.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-30 16:26:08 +01:00
Rob Bradford
5115ad6e56 vmm: config: Support on/off/true/false for all booleans
Migrate missing boolean controls over to the Toggle to handle all
values.

Fixes: #936

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-30 15:21:09 +02:00
Rob Bradford
d5bfa2dfc8 vmm, vhost_user_block: Make parameter names match --disk
Make the --block-backend parameters match the --disk parameters.

Fixes: #898

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-30 15:20:55 +02:00
Sebastien Boeuf
2f0bc06bec vmm: Update default devices names as "internal"
Let's put an underscore "_" in front of each device name to identify
when it has been set internally.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
aaba6e777f vmm: Add virtio-console to the list of Migratable devices
The virtio-console was not added to the list of Migratable devices,
which is fixed from this patch.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
9ab4bb1ae2 devices: serial: Expect an identifier upon device creation
This identifier is chosen from the DeviceManager so that it will manage
all identifiers across the VM, which will ensure uniqueness.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
06487131f9 vm-virtio: pci: Expect an identifier upon device creation
This identifier is chosen from the DeviceManager so that it will manage
all identifiers across the VM, which will ensure uniqueness.

It is based off the name from the virtio device attached to this
transport layer.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
eeb7e10d1f vm-virtio: mmio: Expect an identifier upon device creation
This identifier is chosen from the DeviceManager so that it will manage
all identifiers across the VM, which will ensure uniqueness.

It is based off the name from the virtio device attached to this
transport layer.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
9d84ef5073 vmm: Make the virtio identifier mandatory
Because we know we will need every virtio device to be identified with a
unique id, we can simplify the code by making the identifier mandatory.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
14350f5de4 devices: ioapic: Expect an identifier upon device creation
This identifier is chosen from the DeviceManager so that it will manage
all identifiers across the VM, which will ensure uniqueness.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
556871570e vm-virtio: iommu: Expect an identifier upon device creation
This identifier is chosen from the DeviceManager so that it will manage
all identifiers across the VM, which will ensure uniqueness.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
052eff1ca7 vm-virtio: console: Expect an identifier upon device creation
This identifier is chosen from the DeviceManager so that it will manage
all identifiers across the VM, which will ensure uniqueness.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
354c2a4b3d vm-virtio: vhost-user-net: Expect an identifier upon device creation
This identifier is chosen from the DeviceManager so that it will manage
all identifiers across the VM, which will ensure uniqueness.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
46e0b3ff75 vm-virtio: vhost-user-blk: Expect an identifier upon device creation
This identifier is chosen from the DeviceManager so that it will manage
all identifiers across the VM, which will ensure uniqueness.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
bb7fa71fcb vm-virtio: vhost-user-fs: Expect an identifier upon device creation
This identifier is chosen from the DeviceManager so that it will manage
all identifiers across the VM, which will ensure uniqueness.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
ec5ff395cf vm-virtio: vsock: Expect an identifier upon device creation
This identifier is chosen from the DeviceManager so that it will manage
all identifiers across the VM, which will ensure uniqueness.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
9b53044aae vm-virtio: mem: Expect an identifier upon device creation
This identifier is chosen from the DeviceManager so that it will manage
all identifiers across the VM, which will ensure uniqueness.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
1592a9292f vm-virtio: pmem: Expect an identifier upon device creation
This identifier is chosen from the DeviceManager so that it will manage
all identifiers across the VM, which will ensure uniqueness.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
2e91b73881 vm-virtio: rng: Expect an identifier upon device creation
This identifier is chosen from the DeviceManager so that it will manage
all identifiers across the VM, which will ensure uniqueness.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
9eb7413fab vm-virtio: net: Expect an identifier upon device creation
This identifier is chosen from the DeviceManager so that it will manage
all identifiers across the VM, which will ensure uniqueness.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
be946caf4b vm-virtio: blk: Expect an identifier upon device creation
This identifier is chosen from the DeviceManager so that it will manage
all identifiers across the VM, which will ensure uniqueness.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
ff9c8b847f vmm: Always generate the next device name
Even in the context of "mmio" feature, we need the next device name to
be generated as we need to identify virtio-mmio devices to support
snapshot and restore functionalities.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
8183141399 vmm: Add an identifier to the ioapic device
This will be later used to identify each device used by the VM in order
to perform introspection and snapshot/restore properly.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
e4386c8bb7 vmm: Add an identifier to the virtio-iommu device
This will be later used to identify each device used by the VM in order
to perform introspection and snapshot/restore properly.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
75ddd2a244 vmm: Add an identifier to the --console device
This will be later used to identify each device used by the VM in order
to perform introspection and snapshot/restore properly.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
eac350c454 vmm: Add an identifier to the virtio-mem device
This will be later used to identify each device used by the VM in order
to perform introspection and snapshot/restore properly.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
6802ef5406 vmm: Add an identifier to the --rng device
This will be later used to identify each device used by the VM in order
to perform introspection and snapshot/restore properly.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
d71d52e9b0 vmm: Fix virtio-console creation with virtual IOMMU
If the virtio-console device is supposed to be placed behind the virtual
IOMMU, this must be explicitly propagated through the code.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
b08fde5928 vmm: Fix virtio-rng creation with virtual IOMMU
If the virtio-rng device is supposed to be placed behind the virtual
IOMMU, this must be explicitly propagated through the code.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Sebastien Boeuf
8031ac33c3 vmm: Fix virtio-vsock creation with virtual IOMMU
If the virtio-vsock device is supposed to be placed behind the virtual
IOMMU, this must be explicitly propagated through the code.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 19:34:31 +01:00
Samuel Ortiz
50134969b9 Jenkins: Run musl unit and integration tests on master branch
And use a bumped up container image for that.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-29 17:57:01 +01:00
Samuel Ortiz
ce794f7858 ci: Pass target triple to the test scripts
We pass it to the integration and unit tests script through --libc.
Cargo tests are left unmodified.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-29 17:57:01 +01:00
Samuel Ortiz
33b0e15804 resources: Add musl tools and toolchain to the Dockerfile
And fix the libssl install for building it with musl.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-29 17:57:01 +01:00
Samuel Ortiz
ad9374bd68 dev_cli: Add --libc to the build and test commands
We only support musl and gnu, default is gnu.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-29 17:57:01 +01:00
Rob Bradford
8cef35745b vmm: seccomp: Add fork, gettid and pipe2 syscalls to permitted list
This is needed for self spawning with the musl target.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-29 17:57:01 +01:00
Rob Bradford
ce7678f29f vmm: seccomp: Add tkill syscall to permitted list
This is needed for rebooting on the musl target.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-29 17:57:01 +01:00
Rob Bradford
12758d7fad vmm: seccomp: Add epoll_pwait syscall to permitted list
This is needed for basic operation on the musl target.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-29 17:57:01 +01:00
Samuel Ortiz
86fcd19b8a build: Initial musl support
Fix all build failures and add musl to the gihub workflows.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-29 17:57:01 +01:00
Sebastien Boeuf
a5de49558e vmm: Only allow removal of specific types of virtio device
Now that all virtio devices are assigned with identifiers, they could
all be removed from the VM. This is not something that we want to allow
because it does not make sense for some devices. That's why based on the
device type, we remove the device or we return an error to the user.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-29 13:33:19 +01:00
Sebastien Boeuf
9ed880d74e vmm: Add an identifier to the --fs device
By giving the devices ids this effectively enables the removal of the
device.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-29 13:33:19 +01:00
Sebastien Boeuf
7e0ab6b56d vmm: Fix pmem device creation
The parameters regarding the attachment to the virtio-iommu device was
not propagated correclty, and any modification to the configuration was
not stored back into it.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-29 13:33:19 +01:00
Rob Bradford
3012975c17 tests: Enhance vsock integration test to support hotplug
Test basic vsock functionality works with hotplugged vsock device.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-29 12:44:49 +01:00
Rob Bradford
6c2bca5f1b bin: ch-remote: Add support for adding vsock devices
Add support for adding a vsock device using the HTTP API.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-29 12:44:49 +01:00
Rob Bradford
8de7448d44 vmm: api: Add "add-vsock" API entry point
This allows the hotplugging of vsock devices.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-29 12:44:49 +01:00
Rob Bradford
bf09a1e695 openapi: Add "id" field to VsockConfig
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-29 12:44:49 +01:00
Rob Bradford
a76cf0865f vmm: vm: Remove vsock device from config
When doing device unplug remove the vsock device from the configuration
if present.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-29 12:44:49 +01:00
Rob Bradford
99422324a7 vmm: vm: Add "add_vsock()"
Add the vsock device to the device manager and patch the config to add
the new vsock device.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-29 12:44:49 +01:00
Rob Bradford
1d61c476a1 vmm: device_manager: Add support for hotplugging virtio-vsock devices
Create a new VirtioVsock device and add it to the PCI bus upon hotplug.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-29 12:44:49 +01:00
Rob Bradford
f8501a3bd3 vmm: config: Move --vsock syntax to VsockConfig
This means it can be reused with ch-remote.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-29 12:44:49 +01:00
Sebastien Boeuf
6e049e0da1 vmm: Add an identifier to the --vsock device
It's possible to have multiple vsock devices so in preparation for
hotplug/unplug it is important to be able to have a unique identifier
for each device.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-29 12:44:49 +01:00
Rob Bradford
10348f73e4 vmm, main: Support only zero or one vsock devices
The Linux kernel does not support multiple virtio-vsock devices.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-28 20:07:18 +02:00
Rob Bradford
9d1f95a3cc openapi: Add missing "id" field
NetConfig/DiskConfig/PmemConfig/FsConfig were all missing the id field
in the API yaml file.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-28 18:27:45 +02:00
dependabot-preview[bot]
30e2e51588 build(deps): bump serde_json from 1.0.51 to 1.0.52
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.51 to 1.0.52.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.51...v1.0.52)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-28 14:09:57 +02:00
dependabot-preview[bot]
dd9d0d0440 build(deps): bump micro_http from 0d87a94 to c9ffb90
Bumps [micro_http](https://github.com/firecracker-microvm/micro-http) from `0d87a94` to `c9ffb90`.
- [Release notes](https://github.com/firecracker-microvm/micro-http/releases)
- [Commits](0d87a94c8e...c9ffb90aeb)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-28 10:21:55 +01:00
dependabot-preview[bot]
cdc8493a05 build(deps): bump thiserror from 1.0.15 to 1.0.16
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.15 to 1.0.16.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.15...1.0.16)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-28 08:01:55 +02:00
dependabot-preview[bot]
f5debc4bc0 build(deps): bump libssh2-sys from 0.2.16 to 0.2.17
Bumps [libssh2-sys](https://github.com/alexcrichton/ssh2-rs) from 0.2.16 to 0.2.17.
- [Release notes](https://github.com/alexcrichton/ssh2-rs/releases)
- [Commits](https://github.com/alexcrichton/ssh2-rs/compare/libssh2-sys-0.2.16...libssh2-sys-0.2.17)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-27 09:10:14 +02:00
dependabot-preview[bot]
37dfb4cbb3 build(deps): bump hermit-abi from 0.1.11 to 0.1.12
Bumps [hermit-abi](https://github.com/hermitcore/rusty-hermit) from 0.1.11 to 0.1.12.
- [Release notes](https://github.com/hermitcore/rusty-hermit/releases)
- [Commits](https://github.com/hermitcore/rusty-hermit/compare/hermit-abi-0.1.11...hermit-abi-0.1.12)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-27 09:10:02 +02:00
Muminul Islam
e1a07ce3c4 vmm: vm: Unpark the threads before shutdown when the current state is paused
If the current state is paused that means most of the handles got killed by pthread_kill
We need to unpark those threads to make the shutdown worked. Otherwise
The shutdown API hangs and the API is not responding afterwards. So
before the shutdown call we need to resume the VM make it succeed.

Fixes: #817

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2020-04-27 09:09:12 +02:00
Rob Bradford
1df38daf74 vmm, tests: Make specifying a size optional for virtio-pmem
If a size is specified use it (in particular this is required if the
destination is a directory) otherwise seek in the file to get the size
of the file.

Add a new check that the size is a multiple of 2MiB otherwise the kernel
will reject it.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-24 18:30:05 +01:00
Rob Bradford
7481e4d959 vmm: config: Validate that shared memory is enabled if using vhost-user
Check that if any device using vhost-user (net & disk with
vhost_user=true) or virtio-fs is enabled then check shared memory is
also enabled.

Fixes: #848

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-24 16:01:49 +01:00
Bo Chen
2ac6971a8b vmm: MemoryManager: Cleanup the usage of std::ffi/io/result
Signed-off-by: Bo Chen <chen.bo@intel.com>
2020-04-23 21:39:51 +02:00
Bo Chen
3f42f86d81 vmm: Add the 'shared' and 'hugepages' controls to MemoryConfig
The new 'shared' and 'hugepages' controls aim to replace the 'file'
option in MemoryConfig. This patch also updated all related integration
tests to use the new controls (instead of providing explicit paths to
"/dev/shm" or "/dev/hugepages").

Fixes: #1011

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
Signed-off-by: Bo Chen <chen.bo@intel.com>
2020-04-23 21:39:51 +02:00
dependabot-preview[bot]
d6aa717913 build(deps): bump syn from 1.0.17 to 1.0.18
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.17 to 1.0.18.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.17...1.0.18)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-23 21:32:53 +02:00
Jose Carlos Venegas Munoz
3eaeba4b55 vm-virtio: Fix FS_IO callback for virtio-fs
FS_IO is part of the actions a vhost-user-fs daemon can ask the VMM to
perform on its behalf. It is meant to read/write the content from a file
descriptor directly into a guest memory region. This region can either
be a RAM region or the dedicated cache region for virtio-fs.

The way FS_IO was implemented, it was only expecting the guest physical
address provided through the "cache_offset" field to refer to the cache
region. Unfortunately, this was only implementing FS_IO partially.

This patch extends the existing FS_IO implementation by checking the GPA
against the cache region as a first step, but if it is not part of the
cache region address range, then we fallback onto searching for a RAM
region that could match. If there is a matching RAM region, we retrieve
the corresponding host address to let the VMM read/write from/to it.

Fixes: #1054

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
Signed-off-by: Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
2020-04-23 15:01:28 +01:00
dependabot-preview[bot]
df14a68e87 build(deps): bump smallvec from 1.3.0 to 1.4.0
Bumps [smallvec](https://github.com/servo/rust-smallvec) from 1.3.0 to 1.4.0.
- [Release notes](https://github.com/servo/rust-smallvec/releases)
- [Commits](https://github.com/servo/rust-smallvec/compare/v1.3.0...v1.4.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-23 14:38:39 +02:00
Samuel Ortiz
e685854f16 gh: Separate the build and release jobs
The release one is always skipped for PRs which can be confusing for
e.g. dependabot.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-23 13:45:12 +02:00
Rob Bradford
c790bba905 tests: Migrate from Ubuntu Eoan to Focal
This is the latest LTS release.

Fixes: #989

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-23 13:01:02 +02:00
dependabot-preview[bot]
e525af7a61 build(deps): bump ryu from 1.0.3 to 1.0.4
Bumps [ryu](https://github.com/dtolnay/ryu) from 1.0.3 to 1.0.4.
- [Release notes](https://github.com/dtolnay/ryu/releases)
- [Commits](https://github.com/dtolnay/ryu/compare/1.0.3...1.0.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-23 12:29:14 +02:00
Sebastien Boeuf
3e8a6ba0c0 ci: Ignore test_snapshot_restore
The newly added integration test "test_snapshot_restore" is very
unstable, which causes our CI to fail on most pull requests, which is
not acceptable. That's why we ignore this test until we can fix the
stability issue.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-23 11:38:47 +02:00
dependabot-preview[bot]
9ebf052538 build(deps): bump cc from 1.0.51 to 1.0.52
Bumps [cc](https://github.com/alexcrichton/cc-rs) from 1.0.51 to 1.0.52.
- [Release notes](https://github.com/alexcrichton/cc-rs/releases)
- [Commits](https://github.com/alexcrichton/cc-rs/compare/1.0.51...1.0.52)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-22 22:19:53 +02:00
Sebastien Boeuf
f6b150a34d ci: Add integration test for VM migration
Now that we have multiple virtio devices supporting snapshot and restore
operations, we can add a new integration test to validate the migration
feature works as expected.

The important part is virtio-net as it is used to ssh into the VM to
verify the VM has been restored in the proper state.

This test only works for virtio-mmio for now. Further support for
testing virtio-pci will be added once the virtio-pci transport layer
will support migration.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-22 22:19:31 +02:00
dependabot-preview[bot]
9f08f537e0 build(deps): bump pin-utils from 0.1.0-alpha.4 to 0.1.0
Bumps [pin-utils](https://github.com/rust-lang-nursery/pin-utils) from 0.1.0-alpha.4 to 0.1.0.
- [Release notes](https://github.com/rust-lang-nursery/pin-utils/releases)
- [Commits](https://github.com/rust-lang-nursery/pin-utils/compare/0.1.0-alpha.4...0.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-22 20:18:42 +02:00
Yang Zhong
9c7215d620 docs: Add the vhost-user-blk test doc
This doc describe how to use vhost-user-blk in Cloud Hypervisor
with SPDK. With this doc, we can setup simple test environment on
Ubuntu release.

Signed-off-by: Yang Zhong <yang.zhong@intel.com>
2020-04-22 20:18:23 +02:00
dependabot-preview[bot]
35744375e0 build(deps): bump cc from 1.0.50 to 1.0.51
Bumps [cc](https://github.com/alexcrichton/cc-rs) from 1.0.50 to 1.0.51.
- [Release notes](https://github.com/alexcrichton/cc-rs/releases)
- [Commits](https://github.com/alexcrichton/cc-rs/compare/1.0.50...1.0.51)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-22 17:10:21 +02:00
Yi Sun
4fc75cf2b0 vm-virtio: Implement Snapshottable trait for Console
This patch implements the Snapshottable trait for virtio-console, which
enables migration support for it. A VM with a virtio-console device
attached can be snapshot and then restored without issues.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
Signed-off-by: Yi Sun <yi.y.sun@linux.intel.com>
2020-04-22 14:45:16 +02:00
Yi Sun
d41ce909a2 vm-virtio: Implement Snapshottable trait for Pmem
This brings the migration support to virtio-pmem device.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
Signed-off-by: Yi Sun <yi.y.sun@linux.intel.com>
2020-04-22 14:45:02 +02:00
dependabot-preview[bot]
f626bd60c5 build(deps): bump parking_lot_core from 0.7.1 to 0.7.2
Bumps [parking_lot_core](https://github.com/Amanieu/parking_lot) from 0.7.1 to 0.7.2.
- [Release notes](https://github.com/Amanieu/parking_lot/releases)
- [Changelog](https://github.com/Amanieu/parking_lot/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Amanieu/parking_lot/compare/0.7.1...core-0.7.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-22 09:12:08 +02:00
Martin Xu
5a380a6918 vmm: memory_manager: Support non-power-of-2 block sizes
Replace alignment calculation of start address with functionally
equivalent version that does not assume that the block size is a power
of two.

Signed-off-by: Martin Xu <martin.xu@intel.com>
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-22 09:11:51 +02:00
dependabot-preview[bot]
f8ee89a514 build(deps): bump arc-swap from 0.4.5 to 0.4.6
Bumps [arc-swap](https://github.com/vorner/arc-swap) from 0.4.5 to 0.4.6.
- [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.5...v0.4.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-21 22:08:17 +02:00
Sebastien Boeuf
49322c5ebe vm-virtio: Implement the Snapshottable trait for Net
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
Signed-off-by: Yi Sun <yi.y.sun@linux.intel.com>
2020-04-21 21:25:03 +02:00
Sebastien Boeuf
24c2b67aa4 vm-virtio: Improve virtio-net rx queue processing
The frame buffer must be updated depending on the amount read from it,
which depends on the number and depth of descriptors available at the
time of the processing.

This patch handles this buffer update, and allow for large buffers to be
correctly processed in multiple rounds.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-21 21:25:03 +02:00
Sebastien Boeuf
03dd24978e vm-virtio: Restore queues based on used index
On the restore path, using the available and used indexes read from
memory to fill the Queue structure was a mistake. Indeed, the available
index is written from the guest and it reflects the last available index
in the descriptor table. But the driver might have queued a lot of
buffers which have not yet been used by the device. This leads to a
situation where the next_avail from Queue is completely different from
the one we can read from memory.

Instead, the right way to determine the next_avail index that should be
used by the device is by relying on the used index from the memory. This
index represents the correct information we're looking for as it has
been updated before the snapshot to let the guest know the next index to
process.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-21 21:25:03 +02:00
Sebastien Boeuf
cf707da1a8 vm-virtio: Extend Queue helpers
First, this modifies the existing helpers on how to get indexes for
available and used rings from memory. Instead of updating the queue
through each helper, they are now used as simple getters.

Based on these new getters, we could create a new helper to determine if
the queue has some available descriptors already queued from the driver
side. This helper is going to be particularly helpful when trying to
determine from a virtio thread if a queue is already loaded with some
available buffers that can be used to send information to the guest.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-21 21:25:03 +02:00
Sebastien Boeuf
c22fd39170 vmm: Remove virtio device's userspace mapping on hot-unplug
When a virtio device is dynamically removed from the VM through the
hot-unplug mechanism, every mapping associated with it must be properly
removed.

Based on the previous patches letting a VirtioDevice expose the list of
userspace mappings associated with it, this patch can now remove all the
KVM userspace memory regions through the MemoryManager.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-21 10:02:21 +01:00
Sebastien Boeuf
0a97c25464 vmm: Extend MemoryManager to remove userspace mappings
The same way we added a helper for creating userspace memory mappings
from the MemoryManager, this patch adds a new helper to remove some
previously added mappings.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-21 10:02:21 +01:00
Sebastien Boeuf
b2de1cd523 vm-virtio: Implement shutdown() for virtio-fs
Since the virtio-fs device is backed by a vhost-user process, it is
important to implement the proper shutdown() function from the
VirtioDevice trait, as vhost-user-blk and vhost-user-net do.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-21 10:02:21 +01:00
Sebastien Boeuf
fbcf3a7a7a vm-virtio: Implement userspace_mappings() for virtio-pmem
When hot-unplugging the virtio-pmem from the VM, we don't remove the
associated userspace mapping. This patch will let us fix this in a
following patch. For now, it simply adapts the code so that the Pmem
device knows about the mapping associated with it. By knowing about it,
it can expose it to the caller through the new userspace_mappings()
function.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-21 10:02:21 +01:00
Sebastien Boeuf
b0353992d6 vm-virtio: Implement userspace_mappings() for virtio-fs
This will help when we will implement the hot-unplug of the virtio-fs
device, as we will have to remove correctly the userspace mappings
associated with the device.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-21 10:02:21 +01:00
Sebastien Boeuf
3fb0a02fa2 vm-virtio: Get userspace mappings from VirtioDevice
Introduce new getter function to the VirtioDevice trait, as it will
allow the caller to retrieve the list of userspace mappings associated
with the device.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-21 10:02:21 +01:00
dependabot-preview[bot]
8b823e5e34 build(deps): bump backtrace-sys from 0.1.35 to 0.1.36
Bumps [backtrace-sys](https://github.com/alexcrichton/backtrace-rs) from 0.1.35 to 0.1.36.
- [Release notes](https://github.com/alexcrichton/backtrace-rs/releases)
- [Commits](https://github.com/alexcrichton/backtrace-rs/compare/backtrace-sys-0.1.35...backtrace-sys-0.1.36)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-20 21:49:34 +00:00
Sebastien Boeuf
c23b48888d ci: Factorize virtio-fs hotplug integration tests
There is some duplication between regular and hotplug virtio-fs tests
that can be factorized by adding a simple hotplug flag to choose if each
test should run with or without hotplugging the device.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-20 20:36:26 +02:00
Dean Sheather
f68b08bfdb tests: add integration tests for vm.add-fs route
Adds integration tests with and without dax for the new vm.add-fs route.

Signed-off-by: Dean Sheather <dean@coder.com>
2020-04-20 20:36:26 +02:00
Sebastien Boeuf
18f7789a81 vmm: Add hotplugged virtio devices to the DeviceManager list
The hotplugged virtio devices were not added to the list of virtio
devices from the DeviceManager. This patch fixes it, as it was causing
hotplugged virtio-fs devices from not supporting memory hotplug, since
they were never getting the update as they were not part of the list of
virtio devices held by the DeviceManager.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-20 20:36:26 +02:00
Dean Sheather
c2abadc293 vmm: Add ability to add virtio-fs device post-boot
Adds DeviceManager method `make_virtio_fs_device` which creates a single
device, and modifies `make_virtio_fs_devices` to use this method.

Implements the new `vm.add-fs route`.

Signed-off-by: Dean Sheather <dean@coder.com>
2020-04-20 20:36:26 +02:00
Dean Sheather
bb2139a408 vmm/api: Add vm.add-fs route
Currently unimplemented. Once implemented, this API will allow for
creating virtio-fs devices in the VM after it has booted.

Signed-off-by: Dean Sheather <dean@coder.com>
2020-04-20 20:36:26 +02:00
Sebastien Boeuf
d35e775ed9 vmm: Update KVM userspace mapping when PCI BAR remapping
In the context of the shared memory region used by virtio-fs in order to
support DAX feature, the shared region is exposed as a dedicated PCI
BAR, and it is backed by a KVM userspace mapping.

Upon BAR remapping, the BAR is moved to a different location in the
guest address space, and the KVM mapping must be updated accordingly.

Additionally, we need the VirtioDevice to report the updated guest
address through the shared memory region returned by get_shm_regions().
That's why a new setter is added to the VirtioDevice trait, so that
after the mapping has been updated for KVM, we can tell the VirtioDevice
the new guest address the shared region is located at.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-20 16:01:25 +02:00
Sebastien Boeuf
49cc73a4ca vm-virtio: pci: Make sure to return the correct list of BARs
By adding the shared memory regions to the list of BARs, we make sure
the DeviceManager will register it as a BAR on the PCI bus. Without
this, when PCI BAR reprogramming happens, the PCI bus errors since it
does not know about any BAR at the specified address.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-20 16:01:25 +02:00
Yi Sun
187b1eec8b vm-virtio: Implement the Snapshottable trait for Block
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
Signed-off-by: Yi Sun <yi.y.sun@linux.intel.com>
2020-04-17 19:29:41 +02:00
Samuel Ortiz
a484aa7be6 vm-virtio: Implement the Snapshottable trait for Rng
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Signed-off-by: Yi Sun <yi.y.sun@linux.intel.com>
2020-04-17 19:29:41 +02:00
Sebastien Boeuf
ac7178ef2a vmm: Keep migratable devices list as a Vec
The order the elements are pushed into the list is important to restore
them in the right order. This is particularly important for MmioDevice
(or VirtioPciDevice) and their VirtioDevice counterpart.

A device must be fully ready before its associated transport layer
management can trigger its restoration, which will end up activating the
device in most cases.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-17 19:29:41 +02:00
Sebastien Boeuf
b6fdbf7a44 vm-virtio: Implement Snapshottable trait for MmioDevice
Any virtio device relying on the mmio transport layer can be snapshotted
and restored thanks to this new patch. From the MmioDevice perspective,
it is mainly a matter of saving the information about the virtqueues as
the restore path will need them to activate the device (if needed
because it has been activated before being snapshotted).

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-17 19:29:41 +02:00
Sebastien Boeuf
12fec55064 vm-virtio: Add helpers to update queue indexes
In anticipation for adding snapshot/restore support to virtio devices,
this commit introduces two new helpers updating the available and used
indexes of a queue, relying on the guest memory.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-17 19:29:41 +02:00
Samuel Ortiz
fd45e94510 vm-virtio: Add the ability to serialize a Queue
This commit relies on serde to serialize and deserialize the content of
a Queue structure. This will be useful information to store when
implementing snapshot/restore feature for virtio devices.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Signed-off-by: Yi Sun <yi.y.sun@linux.intel.com>
2020-04-17 19:29:41 +02:00
Sergio Lopez
b7faf4fdc1 vhost_user_fs: Add the WRITE_KILL_PRIV write flag.
Add the WRITE_KILL_PRIV write flag, corresponding to
FUSE_WRITE_KILL_PRIV introduced in 7.31, and use to only remove the
setuid and setgid bits (by switching credentials) conditionally.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2020-04-17 15:42:40 +01:00
Sergio Lopez
0870028fde vhost_user_fs: Add the IOCTL_COMPAT_32 flag
Add the IOCTL_COMPAT_32 flag, corresponding to FUSE_IOCTL_COMPAT_32
introduced in FUSE 7.30.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2020-04-17 15:42:40 +01:00
Sergio Lopez
592cfbafb3 vhost_user_fs: Add the EXPLICIT_INVAL_DATA capability flag
Add EXPLICIT_INVAL_DATA capability flag, corresponding to
FUSE_EXPLICIT_INVAL_DATA introduced in FUSE 7.30.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2020-04-17 15:42:40 +01:00
Sergio Lopez
621ea837fa vhost_user_fs: Add the ZERO_MESSAGE_OPENDIR capability flag
Add ZERO_MESSAGE_OPENDIR capability flag, corresponding to
FUSE_NO_OPENDIR_SUPPORT introduced in FUSE 7.29.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2020-04-17 15:42:40 +01:00
Sergio Lopez
a2830da7c0 vhost_user_fs: Add the CACHE_SYMLINKS flag
Add the CACHE_SYMLINKS flag, corresponding to FUSE_CACHE_SYMLINKS
introduced in FUSE 7.28.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2020-04-17 15:42:40 +01:00
Sergio Lopez
926a414b90 vhost_user_fs: Add support for MAX_PAGES
Add support for MAX_PAGES, corresonding to FUSE_MAX_PAGES introduced
in FUSE 7.28.

This allows us to negotiate with the kernel the maximum number of
pages that we support to transfer in a single request.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2020-04-17 15:42:40 +01:00
Sergio Lopez
747f31d82c vhost_user_fs: Add the ABORT_ERROR flag
Add the ABORT_ERROR flag, corresponding to FUSE_ABORT_ERROR,
introduced in FUSE 7.27.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2020-04-17 15:42:40 +01:00
Sergio Lopez
5eb903a509 vhost_user_fs: Add support for FOPEN_CACHE_DIR
Add support for FOPEN_CACHE_DIR, a flag that allows us to tell the
guest that it's safe to cache a directory, introduced in FUSE 7.28.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2020-04-17 15:42:40 +01:00
Sergio Lopez
97e2d5d266 vhost_user_fs: Add support for CopyFileRange
Add support for the CopyFileRange request, introduced in FUSE 7.28.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2020-04-17 15:42:40 +01:00
Rob Bradford
b8cfdab8b6 pci: configuration: Use correct algorithm for BAR size reporting
When reporting the BAR size it is necessary to return a value that is
encoded such that all the bits are set that represent the mask of the
natural alignment of the field.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-17 15:20:50 +02:00
Rob Bradford
9bd5ec8967 pci, vfio, vm-virtio: Specify a PCI revision ID of 1 for virtio-pci
Add support for specifying the PCI revision in the PCI configuration and
populate this with the value of 1 for virtio-pci devices.

The virtio-pci specification is slightly ambiguous only saying that
transitional (i.e. devices that support legacy and virtio 1.0) should
set this to 0. In practice it seems that software expects the revision
to be set to 1 for modern only devices.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-17 13:46:48 +02:00
Rob Bradford
e7e0e8ac38 vmm, devices: Add firmware debug port device
OVMF and other standard firmwares use I/O port 0x402 as a simple debug
port by writing ASCII characters to it. This is gated under a feature
that is not enabled by default.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-17 12:54:00 +02:00
Sebastien Boeuf
82d0cdff4f vhost_user_net: Simplify match values for handle_event()
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-17 12:52:28 +02:00
Sebastien Boeuf
a517be4eac vhost_user_blk: Add multithreaded multiqueue support
After all the previous refactoring patches, we can finally create
multiple threads under the same backend. This is directly combined with
multiqueues so that it will create one thread per queue.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-17 12:52:28 +02:00
Sebastien Boeuf
13c8283fbe vhost_user_blk: Make everything private when possible
Doing some cleanup as most of the code does not need to be public.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-17 12:52:28 +02:00
Sebastien Boeuf
a31f5f8106 vhost_user_blk: Move disk initialization to VhostUserBlkBackend
Anticipating the follow up patches to run multiple threads for the same
backend, we need the initialization of the disk to happen in the high
level structure VhostUserBlkBackend.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-17 12:52:28 +02:00
Sebastien Boeuf
e78e34b36a vhost_user_blk: Make DiskFile sharable across threads
The DiskFile will need to be shared across multiple threads when running
multiple queues across these threads. That's why it needs to be put
inside an Arc. The reason for the Mutex is because execute() expects a
mutable object implementing Read + Write + Seek. Unfortunately, this
create a contention point as the object needs to be locked from each
thread, reducing the performance gain we will get with multiple threads.

The need for an immutable object would solve this problem, and it will
be addressed later through follow up patches.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-17 12:52:28 +02:00
Sebastien Boeuf
808586ece7 vhost_user_blk: Simplify the code by removing VringWorker
There is no need for retrieving the VringWorker since we don't need to
register some extra file descriptors to the epoll loop.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-17 12:52:28 +02:00
Rob Bradford
ea82632c70 tests: Enhance test_pmem_hotplug to also unplug device
Ensure that the device is removed and the removed device stays away
after a reboot.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-16 17:03:25 +02:00
Rob Bradford
6389418fa6 tests: Enhance test_disk_hotplug to also unplug device
Ensure that the device is removed and the removed device stays away
after a reboot.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-16 17:03:25 +02:00
Rob Bradford
f9a0445c3d vmm: vm: Remove device from configuration after unplug
This ensures that a device that is removed will not reappear after a
reboot.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-16 17:03:25 +02:00
Rob Bradford
444e5c2a04 vmm: device_manager: Generalise NoAvailableVfioDeviceName
We now support assigning device ids for VFIO and virtio-pci devices so
this error can be generalised.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-16 17:03:25 +02:00
Rob Bradford
5bab9c3894 vmm: device_manager: Assign ids to pmem/net/disk devices if absent
If the id has not been provided by the user generate an incrementing id.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-16 17:03:25 +02:00
Rob Bradford
514491a051 vmm: device_manager: Support unplugging virtio-pci devices
Extend the eject_device() method on DeviceManager to also support
virtio-pci devices being unplugged.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-16 17:03:25 +02:00
Rob Bradford
2fa652aa4c vm-virtio: pci: Add virtio_device() accessor
Add an accessor to return the underlying VirtioDevice. This is useful
for managing the removal of the device from internal datastructures when
handling virtio-pci device unplug.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-16 17:03:25 +02:00
Rob Bradford
476e4ce24f vmm: device_manager: Add virtio-pci devices into id to BDF map
In order to support hotplugging there is a map of human readable device
id to PCI BDF map.

As the device id is part of the specific device configuration (e.g.
NetConfig) it is necessary to return the id through from the helper
functions that create the devices through to the functions that add
those devices to the bus. This necessitates changing a great deal of
function prototypes but otherwise has little impact.

Currently only if an id is supplied by the user as part of the device
configuration is it populated into this map. A later commit will
populate with an autogenerated name where none is supplied by the user.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-16 17:03:25 +02:00
Rob Bradford
b38470df4b vmm: config: Add "id" parameter to {Net, Disk, Pmem}Config
This id will be used to unplug the device if the user has chosen an id.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-16 17:03:25 +02:00
Rob Bradford
1beb62ed2d vmm: vm: Don't panic on kernel load error
Rather than panic()ing when we get a kernel loading error populate the
error upwards.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-16 17:03:25 +02:00
dependabot-preview[bot]
a8ec8f3326 build(deps): bump hermit-abi from 0.1.10 to 0.1.11
Bumps [hermit-abi](https://github.com/hermitcore/rusty-hermit) from 0.1.10 to 0.1.11.
- [Release notes](https://github.com/hermitcore/rusty-hermit/releases)
- [Commits](https://github.com/hermitcore/rusty-hermit/compare/hermit-abi-0.1.10...hermit-abi-0.1.11)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-15 18:27:24 +00:00
dependabot-preview[bot]
6cc8248a37 build(deps): bump num_cpus from 1.12.0 to 1.13.0
Bumps [num_cpus](https://github.com/seanmonstar/num_cpus) from 1.12.0 to 1.13.0.
- [Release notes](https://github.com/seanmonstar/num_cpus/releases)
- [Changelog](https://github.com/seanmonstar/num_cpus/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/num_cpus/compare/v1.12.0...v1.13.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-15 18:27:18 +00:00
Rob Bradford
8ff3633782 vm-virtio: pci: Update the BARs used by the VirtioPciDevice
In order to support freeing the memory that is allocated we need to make
sure that we update the internal representation so that free_bars() can
correctly free the memory if the device has its BARs moved.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-15 12:02:19 +02:00
Rob Bradford
56207a0328 pci: Print out details of the BAR moving upon error
In particular include the old and new bases as well as the length.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-15 12:02:19 +02:00
Rob Bradford
a216c2ebd3 vm-virtio: pci: Implement free_bars() for VirtioPciDevice
Implement the free_bars() method from the PciDevice trait which is used
as part of the device removal process. Although there is only one BAR
allocated by VirtioPciDevice simplify the code by using a vector.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-15 12:02:19 +02:00
Rob Bradford
72fdfff15d vmm: device_manager: Remove unused "_mmap_regions" member
Now that ownership of the memory regions used for the virtio-pmem and
vhost-user-fs devices have been moved into those devices it is no longer
necessary to track them inside DeviceManager.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-14 17:46:11 +01:00
Rob Bradford
70ecd6bab4 vmm, virtio: fs: Move freeing of mappped region into device
Move the release of the managed memory region from the DeviceManager to
the vhost-user-fs device. This ensures that the memory will be freed when
the device is unplugged which will lead to it being Drop()ed.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-14 17:46:11 +01:00
Rob Bradford
0c6706a510 vmm, virtio: pmem: Move freeing of mappped region into device
Move the release of the managed memory region from the DeviceManager to
the virtio-pmem device. This ensures that the memory will be freed when
the device is unplugged which will lead to it being Drop()ed.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-14 17:46:11 +01:00
Sebastien Boeuf
6565e478e6 vhost_user_net: Enable multithreaded multiqueue support
By implementing queues_per_thread(), this patch fills the last missing
bit to enable multithreaded multiqueue support for the vhost-user-net
backend implementation.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-14 14:11:41 +02:00
Sebastien Boeuf
1a0a2c0182 vhost_user_backend: Provide the thread ID to handle_event()
By adding a "thread_id" parameter to handle_event(), the backend crate
can now indicate to the backend implementation which thread triggered
the processing of some events.

This is applied to vhost-user-net backend and allows for simplifying a
lot the code since each thread is identical.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-14 14:11:41 +02:00
Sebastien Boeuf
cfffb7edb0 vhost_user_backend: Allow for one exit_event per thread
By adding the "thread_index" parameter to the function exit_event() from
the VhostUserBackend trait, the backend crate now has the ability to ask
the backend implementation about the exit event related to a specific
thread.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-14 14:11:41 +02:00
Sebastien Boeuf
b927dceed8 vhost_user_net: Prepare for multithreaded support
In order to prepare for the support of multithreaded multiqueues, the
structure VhostUserNetThread is simplified to hold only one RX queue,
one TX queue, and one TAP interface.

Following this change, VhostUserNetBackend now holds a list of threads
instead of going through each thread to handle multiqueues.

These changes decouple neatly the abstraction between the backend and
each thread. This allows for a lot of simplification since we now know
all threads are identical, hence the handling of events becomes very
straightforward.

One important point is that each thread can be locked when in use,
without causing any contention with other threads since the backend
doesn't need to be locked anymore.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-14 14:11:41 +02:00
Sebastien Boeuf
cd2b03f6ed vhost_user_backend: Return a list of vring workers
Now that multiple worker threads can be run from the backend crate, it
is important that each backend implementation can access every worker
thread.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-14 14:11:41 +02:00
Sebastien Boeuf
d9eec0de14 vhost_user_backend: Add the ability to start multiple threads
In order to support multiqueues running on multiple threads for
increasing the IO performances, this commit introduces a new function
queues_per_thread() to the VhostUserBackend trait.

This gives each backend implementation the opportunity to define which
queues belong to which thread.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-14 14:11:41 +02:00
Sebastien Boeuf
40e4dc6339 vhost_user_backend: Change handle_event as immutable
By changing the mutability of this function, after adapting all
backends, we should be able to implement multithreads with
multiqueues support without hitting a bottleneck on the backend
locking.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-14 14:11:41 +02:00
Sebastien Boeuf
8f434df1fb vhost_user: Adapt backends to let handle_event be immutable
Both blk, net and fs backends have been updated to avoid the requirement
of having handle_event(&mut self). This will allow the backend crate to
avoid taking a write lock onto the backend object, which will remove the
potential contention point when multiple threads will be handling
multiqueues.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-14 14:11:41 +02:00
Sebastien Boeuf
b1554642e4 vmm: seccomp: Add missing mremap() syscall
While testing self spawned vhost-user backends, it appeared that the
backend was aborting due to a missing system call in the seccomp
filters. mremap() was the culprit and this patch simply adds it to the
whitelist.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-14 14:11:41 +02:00
dependabot-preview[bot]
886c0f9093 build(deps): bump libc from 0.2.68 to 0.2.69
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.68 to 0.2.69.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.68...0.2.69)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-14 09:27:04 +01:00
dependabot-preview[bot]
6c164c761b build(deps): bump thiserror from 1.0.14 to 1.0.15
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.14 to 1.0.15.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.14...1.0.15)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-13 08:33:58 +02:00
dependabot-preview[bot]
0071ac8c30 build(deps): bump parking_lot from 0.10.0 to 0.10.2
Bumps [parking_lot](https://github.com/Amanieu/parking_lot) from 0.10.0 to 0.10.2.
- [Release notes](https://github.com/Amanieu/parking_lot/releases)
- [Changelog](https://github.com/Amanieu/parking_lot/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Amanieu/parking_lot/compare/0.10.0...0.10.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-11 01:25:39 +02:00
dependabot-preview[bot]
2b7fbcb99a build(deps): bump lock_api from 0.3.3 to 0.3.4
Bumps [lock_api](https://github.com/Amanieu/parking_lot) from 0.3.3 to 0.3.4.
- [Release notes](https://github.com/Amanieu/parking_lot/releases)
- [Changelog](https://github.com/Amanieu/parking_lot/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Amanieu/parking_lot/compare/lock_api-0.3.3...lock_api-0.3.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-10 19:20:27 +02:00
dependabot-preview[bot]
d1155c7c7f build(deps): bump parking_lot_core from 0.7.0 to 0.7.1
Bumps [parking_lot_core](https://github.com/Amanieu/parking_lot) from 0.7.0 to 0.7.1.
- [Release notes](https://github.com/Amanieu/parking_lot/releases)
- [Changelog](https://github.com/Amanieu/parking_lot/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Amanieu/parking_lot/compare/0.7.0...0.7.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-10 19:20:17 +02:00
Rob Bradford
28abfa9de5 vmm: openapi: Mark "initramfs" field nullable
This should make it a pointer in the Go generated code so that it will
be ommitted and thus not populated with an unhelpful default value.

Fixes: #1015

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-09 23:25:18 +02:00
Rob Bradford
c260640fd5 vmm: config: Use Default::default() value for initramfs field
This ensures that the field is filled with None when it is not specified
as part of the deserialisation step.

Fixes: #1015

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-09 17:28:45 +02:00
Alejandro Jimenez
4617aefd60 tests: Test initramfs loading with PVH boot
Add an integration test to verify loading an initramfs using the
PVH boot protocol.

Signed-off-by: Alejandro Jimenez <alejandro.j.jimenez@oracle.com>
2020-04-09 17:28:03 +02:00
Alejandro Jimenez
7134f3129f vmm: Allow PVH boot with initramfs
We can now allow guests that specify an initramfs to boot
using the PVH boot protocol.

Signed-off-by: Alejandro Jimenez <alejandro.j.jimenez@oracle.com>
2020-04-09 17:28:03 +02:00
Alejandro Jimenez
0fc3936448 arch: Support loading initramfs with PVH boot protocol
Fill and write to guest memory the necessary boot module
structure to allow a guest using the PVH boot protocol
to load an initramfs image.

Signed-off-by: Alejandro Jimenez <alejandro.j.jimenez@oracle.com>
2020-04-09 17:28:03 +02:00
dependabot-preview[bot]
b9f193703a build(deps): bump smallvec from 1.2.0 to 1.3.0
Bumps [smallvec](https://github.com/servo/rust-smallvec) from 1.2.0 to 1.3.0.
- [Release notes](https://github.com/servo/rust-smallvec/releases)
- [Commits](https://github.com/servo/rust-smallvec/compare/v1.2.0...v1.3.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-09 06:07:36 +00:00
Rob Bradford
2d3f518c72 vmm: config: Error if both socket and path are specified for a disk
This allows the validation of this requirement for both command line
booted VMs and those booted via the API.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-08 12:06:09 +01:00
Rob Bradford
eeb7e2529d vmm: config: Move max vCPUs > boot vCPUs check to validate()
This allows the validation of this requirement for both command line
booted VMs and those booted via the API.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-08 12:06:09 +01:00
Rob Bradford
12edb24678 vmm: config: Validate that serial/console file mode has a path
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-08 12:06:09 +01:00
Rob Bradford
31928fb103 main: Consistently use eprintln!() for error messages
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-08 12:06:09 +01:00
Rob Bradford
11dd609fa5 main: Only try and parse VM options on VM boot path
As the VmConfig::Parse() also does validation work it only make sense to
parse the VM options on the VM boot path only.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-08 12:06:09 +01:00
Rob Bradford
aaf382eee2 vmm: Move kernel check to VmConfig::validate() method
Replace the existing VmConfig::valid() check with a call into
.validate() as part of earlier config setup or boot API checks.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-08 12:06:09 +01:00
Rob Bradford
3b0da2d895 vmm: vm: Validate configuration on API boot
When performing an API boot validate the configuration. For now only
some very basic validation is performed but in subsequent commits
the validation will be extended.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-08 12:06:09 +01:00
Rob Bradford
99b2ada4d0 vmm: Start splitting configuration parsing and validation
The configuration comes from a variety of places (commandline, REST API
and restore) however some validation was only happening on the command
line parsing path.  Therefore introduce a new ability to validate the
configuration before proceeding so that this can be used for commandline
and API boots.

For now move just the console and serial output mode validation under
the new validation API.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-08 12:06:09 +01:00
Sebastien Boeuf
0ea706faf5 vmm: openapi: Update OpenAPI definition with RestoreConfig
Making sure the OpenAPI definition is up to date with newly added
structure and parameters to support VM restoration.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-08 10:56:14 +02:00
Sebastien Boeuf
8d9d22436a vmm: Add "prefault" option when restoring
Now that the restore path uses RestoreConfig structure, we add a new
parameter called "prefault" to it. This will give the user the ability
to populate the pages corresponding to the mapped regions backed by the
snapshotted memory files.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-08 10:56:14 +02:00
Sebastien Boeuf
a517ca23a0 vmm: Move restore parameters into common RestoreConfig structure
The goal here is to move the restore parameters into a dedicated
structure that can be reused from the entire codebase, making the
addition or removal of a parameter easier.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-08 10:56:14 +02:00
Sebastien Boeuf
6712958f23 vmm: memory: Add prefault option when creating region
When CoW can be used, the VM restoration time is reduced, but the pages
are not populated. This can lead to some slowness from the guest when
accessing these pages.

Depending on the use case, we might prefer a slower boot time for better
performances from guest runtime. The way to achieve this is to prefault
the pages in this case, using the MAP_POPULATE flag along with CoW.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-08 10:56:14 +02:00
Sebastien Boeuf
b2cdee80b6 vmm: memory: Restore with Copy-on-Write when possible
This patch extends the previous behavior on the restore codepath.
Instead of copying the memory regions content from the snapshot files
into the new memory regions, the VMM will use the snapshot region files
as the backing files behind each mapped region. This is done in order to
reduce the time for the VM to be restored.

When the source VM has been initially started with a backing file, this
means it has been mapped with the MAP_SHARED flag. For this case, we
cannot use the CoW trick to speed up the VM restore path and we simply
fallback onto the copy of the memory regions content.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-08 10:56:14 +02:00
Sebastien Boeuf
d771223b2f vmm: memory: Extend new() to support external backing files
Whenever a MemoryManager is restored from a snapshot, the memory regions
associated with it might need to directly back the mapped memory for
increased performances. If that's the case, a list of external regions
is provided and the MemoryManager should simply ignore what's coming
from the MemoryConfig.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-08 10:56:14 +02:00
Sebastien Boeuf
ee5a041a0f vmm: memory: Add Copy-on-Write parameter when creating region
Now that we can choose specific mmap flags for the guest RAM, we create
a new parameter "copy_on_write" meaning that the memory mappings backed
by a file should be performed with MAP_PRIVATE instead of MAP_SHARED.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-08 10:56:14 +02:00
Sebastien Boeuf
be4e1e8712 vmm: memory: Use fine grained mmap wrapper
In order to anticipate the need for special mmap flags when memory
mapping the guest RAM, we need to switch from from_file() wrapper to
build() wrapper.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-08 10:56:14 +02:00
dependabot-preview[bot]
f0ab002ef1 build(deps): bump openssl-sys from 0.9.54 to 0.9.55
Bumps [openssl-sys](https://github.com/sfackler/rust-openssl) from 0.9.54 to 0.9.55.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-sys-v0.9.54...openssl-sys-v0.9.55)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-08 06:04:24 +00:00
Sebastien Boeuf
b9f9f01fcc vmm: Extend seccomp filters to allow snapshot/restore
A few KVM ioctls were missing in order to perform both snapshot and
restore while keeping seccomp enabled.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-07 12:26:10 +02:00
Sebastien Boeuf
6eb721301c vmm: Enable restore feature
This connects the dots together, making the request from the user reach
the actual implementation for restoring the VM.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-07 12:26:10 +02:00
Sebastien Boeuf
53613319cc vmm: Enable snapshot feature
This connects the dots together, making the request from the user reach
the actual implementation for snapshotting the VM.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-07 12:26:10 +02:00
Samuel Ortiz
2cd0bc0a2c vmm: Create initial VM from its snapshot
The MemoryManager is somehow a special case, as its restore() function
was not implemented as part of the Snapshottable trait. Instead, and
because restoring memory regions rely both on vm.json and every memory
region snapshot file, the memory manager is restored at creation time.
This makes the restore path slightly different from CpuManager, Vcpu,
DeviceManager and Vm, but achieve the correct restoration of the
MemoryManager along with its memory regions filled with the correct
content.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-07 12:26:10 +02:00
Samuel Ortiz
b55b83c6e8 vmm: vm: Implement the Transportable trait
This is only implementing the send() function in order to store all Vm
states into a file.

This needs to be extended for live migration, by adding more transport
methods, and also the recv() function must be implemented.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-07 12:26:10 +02:00
Samuel Ortiz
1ed357cf34 vmm: vm: Implement the Snapshottable trait
By aggregating snapshots from the CpuManager, the MemoryManager and the
DeviceManager, Vm implements the snapshot() function from the
Snapshottable trait.
And by restoring snapshots from the CpuManager, the MemoryManager and
the DeviceManager, Vm implements the restore() function from the
Snapshottable trait.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Signed-off-by: Yi Sun <yi.y.sun@linux.intel.com>
2020-04-07 12:26:10 +02:00
Samuel Ortiz
20ba271b6c vmm: memory_manager: Implement the Transportable trait
This implements the send() function of the Transportable trait, so that
the guest memory regions can be saved into one file per region.

This will need to be extended for live migration, as it will require
other transport methods and the recv() function will need to be
implemented too.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-07 12:26:10 +02:00
Yi Sun
e606112cef vmm: memory_manager: Implement the Snapshottable trait
In order to snapshot the content of the guest RAM, the MemoryManager
must implement the Snapshottable trait.

Signed-off-by: Yi Sun <yi.y.sun@linux.intel.com>
2020-04-07 12:26:10 +02:00
Yi Sun
50b3f008d1 vmm: cpu: Implement the Snapshottable trait
Implement the Snapshottable trait for Vcpu, and then implements it for
CpuManager. Note that CpuManager goes through the Snapshottable
implementation of Vcpu for every vCPU in order to implement the
Snapshottable trait for itself.

Signed-off-by: Yi Sun <yi.y.sun@linux.intel.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-07 12:26:10 +02:00
Sebastien Boeuf
f787c409c4 vmm: cpu: Factorize vcpu starting code
Anticipating the need for a slightly different function for restoring
vCPUs, this patch factorizes most of the vCPU creation, so that it can
be reused for migration purposes.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-07 12:26:10 +02:00
Cathy Zhang
722f9b6628 vmm: cpu: Get and set KVM vCPU state
These two new helpers will be useful to capture a vCPU state and being
able to restore it at a later time.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-07 12:26:10 +02:00
Cathy Zhang
13756490b5 vmm: cpu: Track all Vcpus through CpuManager
In anticipation for the CpuManager to aggregate all Vcpu snapshots
together, this change makes sure the CpuManager has a handle onto
every vCPU.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-07 12:26:10 +02:00
Samuel Ortiz
a0d5dbce6c vmm: device_manager: Implement the Snapshottable trait
Based on the list of Migratable devices stored by the DeviceManager, the
DeviceManager can implement the Snapshottable trait by aggregating all
devices snapshots together.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-07 12:26:10 +02:00
Yi Sun
93d3abfd6e vmm: device_manager: Make serial and ioapic devices migratable
Serial and Ioapic both implement the Migratable trait, hence the
DeviceManager can store them in the list of Migratable devices.

Signed-off-by: Yi Sun <yi.y.sun@linux.intel.com>
2020-04-07 12:26:10 +02:00
Samuel Ortiz
12b036a824 Cargo: Update dependencies for the KVM serialization work
We need the project to rely on kvm-bindings and kvm-ioctls branches
which include the serde derive to be able to serialize and deserialize
some KVM structures.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-07 12:26:10 +02:00
Yang Zhong
183529d024 vmm: Cleanup warning from build
Remove unnecessary parentheses from code and this will cleanup
the warning from cargo build.

Signed-off-by: Yang Zhong <yang.zhong@intel.com>
2020-04-07 09:45:31 +02:00
Rob Bradford
22958261aa main: Print human readable error for command line error
Fixes: #367

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
c7dfbd8a84 vmm: config: Implement fmt::Display for error
Fixes: #367

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
d8119fda13 vmm: config: Remove unused error entries
These entries are not currently used.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
1a10f16ad0 vmm: config: Consolidate size parsing code
The parse_size helper function can now be consolidated into the
ByteSized FromStr implementation.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
f449486b9b vmm: config: Make toggle parsing more tolerant
Support "true" and "false" as well as well as capitalised forms.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
a4e0ce58c7 vmm: config: Consolidate on/off parsing
Now all parsing code makes use of the Toggle and it's FromStr support
move the helper function into the from_str() implementation.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
c731a943d4 vmm: config: Port vsock to OptionParser
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
37264cf21b vmm: config: Add unit testing for vsock
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
8665898ff3 vmm: config: Port device parsing to OptionParser
Also make the "path" option required and generate an error if it is not
provided.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
a85e2fa735 vmm: config: Add unit test for VFIO device parsing
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
bed282b801 vmm: config: Add "valueless" options to OptionParser
Valueless options are those like "off" or "tty" as used by the console
options.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
2ae3392d32 vmm: config: Port console parsing to OptionParser
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
143d63c88e vmm: config: Add unit test for console parsing
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
5ab58e743a vmm: config: Port pmem option to OptionParser
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
233ad78b3a vmm: config: Add parsing test for pmem
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
13dc637350 vmm: config: Port filesystem parsing to OptionParser
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
7a071c28db vmm: config: Implement unit testing for virtio-fs parsing
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
e4cd3072d4 vmm: config: Port RNG options to OptionParser
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
708dbb973a vmm: config: Add RNG parsing unit test
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
057e71d266 vmm: config: Accept empty value strings
The integration tests and documentation make use of empty value strings
like "--net tap=" accept them but return None so that the default value
will be used as expected.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
218c780f67 vmm: config: Port network parsing to OptionParser
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
a5747a843e net_util: Implement FromStr for MacAddr
This allows it to be used with str::parse().

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
8754720e2d vmm: config: Add unit test for net parsing
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
224e3ddef4 vmm: config: Switch disk parsing to OptionParser
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
9e10244716 vmm: config: Add unit test for disk parsing
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
e40ae6274b vmm: config: Port memory option parsing to OptionParser
This simplifies the parsing of the option by using OptionParser along
with its automatic conversion behaviour.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
be32065aa4 vmm: config: Add "ByteSized" type for simplifying parsing of byte sizes
Byte sizes are quantities ending in "K", "M", "G" and by implementing
this type with a FromStr implementation the values can be converted
using .parse().

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
f01bd7d56d vmm: config: Implement FromStr for HotplugMethod
This allows the use of .parse() to automatically convert the string to
the enum.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
746138039d vmm: config: Add a Toggle type for "on/off" strings
Some of the config parameters take an "on" or "off". Add a way to
neatly parse that.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
929142bc2e vmm: config: Add memory parsing unit test
Before porting over to OptionParser add a unit test to validate the
current memory parsing code. This showed up a bug where the "size=" was
always required. Temporarily resolve this by assigning the string a
default value which will later be replaced when the code is refactored.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
68203ea414 vmm: config: Port CPU parsing to OptionParser
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
9e6a2825ba vmm: config: Add unit test for CPU parsing
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
Rob Bradford
9e7231cd69 vmm: config: Introduce basic OptionParser
This will be used to simplify and consolidate much of the parsing code
used for command line parameters.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-06 10:31:24 +01:00
dependabot-preview[bot]
1e20b5727d build(deps): bump serde_json from 1.0.50 to 1.0.51
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.50 to 1.0.51.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.50...v1.0.51)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-06 05:31:07 +00:00
dependabot-preview[bot]
baf4850052 build(deps): bump serde_derive from 1.0.105 to 1.0.106
Bumps [serde_derive](https://github.com/serde-rs/serde) from 1.0.105 to 1.0.106.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.105...v1.0.106)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-04 14:22:07 +00:00
dependabot-preview[bot]
00230905ff build(deps): bump serde from 1.0.105 to 1.0.106
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.105 to 1.0.106.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.105...v1.0.106)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-04 13:58:41 +00:00
Samuel Ortiz
447af8e702 vmm: vm: Factorize the device and cpu managers creation routine
Into a new_from_memory_manager() routine.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-03 18:05:18 +01:00
Samuel Ortiz
c73c9b112c vmm: vm: Open kernel and initramfs once all managers are created
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-03 18:05:18 +01:00
Samuel Ortiz
0646a90626 vmm: cpu: Pass CpusConfig to simplify the new() prototype
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-03 18:05:18 +01:00
Samuel Ortiz
b584ec3fb3 vmm: memory_manager: Own the system allocator
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-03 18:05:18 +01:00
Samuel Ortiz
ef2b11ee6c vmm: memory_manager: Pass MemoryConfig to simplify the new() prototype
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-03 18:05:18 +01:00
Samuel Ortiz
622f3f8fb6 vmm: vm: Avoid ioapic variable creation
For a more readable VM creation routine.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-03 18:05:18 +01:00
Samuel Ortiz
164e810069 vmm: cpu: Move CPUID patching to CpuManager
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-03 18:05:18 +01:00
Samuel Ortiz
1a2c1f9751 vmm: vm: Factorize the KVM setup code
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-03 18:05:18 +01:00
Samuel Ortiz
3eb11069d0 arch: regs: Rename and export create_msr_entries
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-03 18:05:18 +01:00
Samuel Ortiz
c3a3490331 arch: regs: Make create_msr_entries more readable
By using simple macros.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-03 18:05:18 +01:00
Samuel Ortiz
7a50646c02 vmm: device_manager: Convert migratable_devices to a map
We must be able to map a migratable component id to its device.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-03 18:05:18 +01:00
Rob Bradford
8ba37a98a7 .gitignore: Add build directory
This directory contains the source code for crates that we pull in and
should be excluded.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-04-03 15:44:14 +01:00
Yi Sun
b3e4111e1d devices: serial: Implement the Snapshottable trait
Signed-off-by: Yi Sun <yi.y.sun@linux.intel.com>
2020-04-02 19:02:57 +02:00
Yi Sun
98741573e7 devices: ioapic: Implement the Snapshottable trait
Signed-off-by: Yi Sun <yi.y.sun@linux.intel.com>
2020-04-02 19:02:57 +02:00
Sebastien Boeuf
3ef1c00cfb ch-remote: Fix snapshot and restore subcommands
So that they are listed and can be used as expected.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-02 17:55:30 +01:00
Sebastien Boeuf
dc97b67dac main: Fix restore CLI
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-02 17:55:30 +01:00
Sebastien Boeuf
859a96181f ch-remote: Add --restore option
Introduce restore wrapper to ch-remote.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-02 13:24:25 +01:00
Sebastien Boeuf
35c0ea6c25 ch-remote: Add --snapshot option
Introduce the snapshot wrapper to ch-remote.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-02 13:24:25 +01:00
Samuel Ortiz
fe2d884605 main: Support VM restore from the command line
Through the new CLI --restore option.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-02 13:24:25 +01:00
Samuel Ortiz
8f300bed83 vmm: api: Add a /api/v1/vm.restore endpoint
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-02 13:24:25 +01:00
Samuel Ortiz
92c73c3b78 vmm: Add a VmRestore command
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-02 13:24:25 +01:00
Samuel Ortiz
39d4f817f0 vmm: http: Add a /api/v1/vm.snapshot endpoint
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-04-02 13:24:25 +01:00
Samuel Ortiz
cf8f8ce93a vmm: api: Add a Snapshot command
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Signed-off-by: Yi Sun <yi.y.sun@linux.intel.com>
2020-04-02 13:24:25 +01:00
Sebastien Boeuf
452475c280 vmm: Add migration helpers
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-02 13:24:25 +01:00
Samuel Ortiz
1b1a2175ca vm-migration: Define the Snapshottable and Transportable traits
A Snapshottable component can snapshot itself and
provide a MigrationSnapshot payload as a result.

A MigrationSnapshot payload is a map of component IDs to a list of
migration sections (MigrationSection). As component can be made of
several Migratable sub-components (e.g. the DeviceManager and its
device objects), a migration snapshot can be made of multiple snapshot
itself.
A snapshot is a list of migration sections, each section being a
component state snapshot. Having multiple sections allows for easier and
backward compatible migration payload extensions.

Once created, a migratable component snapshot may be transported and this
is what the Transportable trait defines, through 2 methods: send and recv.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Signed-off-by: Yi Sun <yi.y.sun@linux.intel.com>
2020-04-02 13:24:25 +01:00
Sebastien Boeuf
2d17f4384a vmm: seccomp: Add missing open() syscall
On some systems, the open() system call is used by Cloud-Hypervisor,
that's why it should be part of the seccomp filters whitelist.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-04-02 09:56:48 +02:00
dependabot-preview[bot]
bf135a29f7 build(deps): bump linux-loader from 2adddce to 61d95eb
Bumps [linux-loader](https://github.com/rust-vmm/linux-loader) from `2adddce` to `61d95eb`.
- [Release notes](https://github.com/rust-vmm/linux-loader/releases)
- [Commits](2adddce25b...61d95eb67b)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-02 06:35:44 +00:00
dependabot-preview[bot]
da4aaee778 build(deps): bump vmm-sys-util from 0.4.0 to 0.5.0
Bumps [vmm-sys-util](https://github.com/rust-vmm/vmm-sys-util) from 0.4.0 to 0.5.0.
- [Release notes](https://github.com/rust-vmm/vmm-sys-util/releases)
- [Changelog](https://github.com/rust-vmm/vmm-sys-util/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-vmm/vmm-sys-util/compare/v0.4.0...v0.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-01 12:02:19 +00:00
dependabot-preview[bot]
6a934c32ee build(deps): bump proc-macro-hack from 0.5.14 to 0.5.15
Bumps [proc-macro-hack](https://github.com/dtolnay/proc-macro-hack) from 0.5.14 to 0.5.15.
- [Release notes](https://github.com/dtolnay/proc-macro-hack/releases)
- [Commits](https://github.com/dtolnay/proc-macro-hack/compare/0.5.14...0.5.15)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-31 15:56:27 +00:00
dependabot-preview[bot]
3b891cd304 build(deps): bump micro_http from e89ed14 to 0d87a94
Bumps [micro_http](https://github.com/firecracker-microvm/micro-http) from `e89ed14` to `0d87a94`.
- [Release notes](https://github.com/firecracker-microvm/micro-http/releases)
- [Commits](e89ed14707...0d87a94c8e)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-31 07:31:12 +00:00
dependabot-preview[bot]
d5199ad91d build(deps): bump anyhow from 1.0.27 to 1.0.28
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.27 to 1.0.28.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.27...1.0.28)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-31 06:00:23 +00:00
dependabot-preview[bot]
6a0b4d7a7c build(deps): bump proc-macro2 from 1.0.9 to 1.0.10
Bumps [proc-macro2](https://github.com/alexcrichton/proc-macro2) from 1.0.9 to 1.0.10.
- [Release notes](https://github.com/alexcrichton/proc-macro2/releases)
- [Commits](https://github.com/alexcrichton/proc-macro2/compare/1.0.9...1.0.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-30 20:59:50 +00:00
dependabot-preview[bot]
31bbe0e509 build(deps): bump hermit-abi from 0.1.9 to 0.1.10
Bumps [hermit-abi](https://github.com/hermitcore/rusty-hermit) from 0.1.9 to 0.1.10.
- [Release notes](https://github.com/hermitcore/rusty-hermit/releases)
- [Commits](https://github.com/hermitcore/rusty-hermit/compare/hermit-abi-0.1.9...hermi-abi-0.1.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-30 06:59:35 +00:00
dependabot-preview[bot]
7f098168ac build(deps): bump hermit-abi from 0.1.8 to 0.1.9
Bumps [hermit-abi](https://github.com/hermitcore/rusty-hermit) from 0.1.8 to 0.1.9.
- [Release notes](https://github.com/hermitcore/rusty-hermit/releases)
- [Commits](https://github.com/hermitcore/rusty-hermit/compare/hermit-abi-0.1.8...hermit-abi-0.1.9)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-30 05:16:35 +00:00
dependabot-preview[bot]
00a1eced55 build(deps): bump thiserror from 1.0.13 to 1.0.14
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.13 to 1.0.14.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.13...1.0.14)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-29 19:25:57 +00:00
dependabot-preview[bot]
f24b74246d build(deps): bump serde_json from 1.0.48 to 1.0.50
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.48 to 1.0.50.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.48...v1.0.50)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-28 20:33:37 +00:00
Sebastien Boeuf
e4ea8b0bef vmm: Add missing syscalls to the seccomp filters
Both clock_gettime and gettimeofday syscalls where missing when running
Cloud-Hypervisor on a Linux host without vDSO enabled. On a system with
vDSO enabled, the syscalls performed by vDSO were not filtered, that's
why we didn't have to whitelist them.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-27 16:50:52 +00:00
Eryu Guan
33be24bd5a vhost-user-fs: return EINVAL if req is out of range in fs_slave_mmap/unmap/sync
Return libc::EINVAL instead of custom "Wrong offset" error, as mmap(2)
returns EINVAL when offset/len is invalid.

Signed-off-by: Eryu Guan <eguan@linux.alibaba.com>
2020-03-27 11:27:56 +01:00
Eryu Guan
78b5cbc63a vhost-user-fs: validate fs_slave_map/unmap/sync request
In fs_slave_map/unmap/sync, we only made sure offset < cache_size, but
didn't validate (offset + len). We should ensure [offset, offset+len]
is within cache range as well.

Signed-off-by: Eryu Guan <eguan@linux.alibaba.com>
2020-03-27 11:27:56 +01:00
Sebastien Boeuf
0c29c2ec49 ci: Extend VFIO test with memory hotplug
One more time, we're extending the VFIO integration test to verify that
a VFIO device passed through a VM is still usable after some new memory
has been hotplugged.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-27 09:35:39 +01:00
Sebastien Boeuf
9e18177654 vmm: Add memory hotplug support to VFIO PCI devices
Extend the update_memory() method from DeviceManager so that VFIO PCI
devices can update their DMA mappings to the physical IOMMU, after a
memory hotplug has been performed.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-27 09:35:39 +01:00
Sebastien Boeuf
cc67131ecc vmm: Retrieve new memory region when memory is extended
Whenever the memory is resized, it's important to retrieve the new
region to pass it down to the device manager, this way it can decide
what to do with it.

Also, there's no need to use a boolean as we can instead use an Option
to carry the information about the region. In case of virtio-mem, there
will be no region since the whole memory has been reserved up front by
the VMM at boot. This means only the ACPI hotplug will return a region
and is the only method that requires the memory to be updated from the
device manager.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-27 09:35:39 +01:00
Sebastien Boeuf
e4a034aef9 vfio: Add support for memory hotplug
Whenever the guest memory is extended, the IOMMU mappings related to
each VFIO device must be updated. This allows subsequent RAM accesses
to be authorized by the physical IOMMU for the newly added RAM region.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-27 09:35:39 +01:00
Samuel Ortiz
8fc7bf2953 vmm: Move to the latest linux-loader
Commit 2adddce2 reorganized the crate for a cleaner multi architecture
(x86_64 and aarch64) support.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-03-27 08:48:20 +01:00
Sebastien Boeuf
785812d976 vmm: Fallback to legacy boot if PVH is enabled along with initramfs
For now, the codebase does not support booting from initramfs with PVH
boot protocol, therefore we need to fallback to the legacy boot.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-26 11:59:03 +01:00
Damjan Georgievski
5157ba10d1 resources: enable initramfs in kernel config
so that we can use the kernel compiled for the integration tests

Signed-off-by: Damjan Georgievski <gdamjan@gmail.com>
2020-03-26 11:59:03 +01:00
Damjan Georgievski
3b470d4f4b tests: add support for initramfs
the integration test creates an initramfs image based on AlpineLinux mini root filesystem
with a simple /init script that just echoes a string to the console. The string
is passed via the kernel cmdline as an environment variable.

Signed-off-by: Damjan Georgievski <gdamjan@gmail.com>
2020-03-26 11:59:03 +01:00
Damjan Georgievski
6cce7b9560 arch: load initramfs and populate zero page
* load the initramfs File into the guest memory, aligned to page size
* finally setup the initramfs address and its size into the boot params
  (in configure_64bit_boot)

Signed-off-by: Damjan Georgievski <gdamjan@gmail.com>
2020-03-26 11:59:03 +01:00
Damjan Georgievski
1f9bc68c54 openapi: Add initramfs support
added InitramfsConfig property to the REST API spec

Signed-off-by: Damjan Georgievski <gdamjan@gmail.com>
2020-03-26 11:59:03 +01:00
Damjan Georgievski
4db252b418 main, vmm: add --initramfs cli option
currently unused, the initramfs argument is added to the cli,
and stored in vmm::config:VmConfig as an Option(InitramfsConfig(PathBuf))

Signed-off-by: Damjan Georgievski <gdamjan@gmail.com>
2020-03-26 11:59:03 +01:00
Damjan Georgievski
0ce7de3ef5 arch: provide mechanism to get page size
This is a copy of the same code in vm-allocator, until a better place is
found for general use.

Signed-off-by: Damjan Georgievski <gdamjan@gmail.com>
2020-03-26 11:59:03 +01:00
Rob Bradford
4b110d5b73 tests: Add integration test for hotplugging network device
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 17:58:06 +01:00
Rob Bradford
f3f4d07595 ch-remote: Add support for hotplugging network devices
Call the new HTTP API for hotplugging network devices using the same
syntax as coldplug.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 17:58:06 +01:00
Rob Bradford
c73c31b66e docs: Update API documentation to include "vm.add-net"
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 17:58:06 +01:00
Rob Bradford
6244beb9d5 openapi: Add "vm.add-net" entry point
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 17:58:06 +01:00
Rob Bradford
57c3fa4b1e vmm: Add "add-net" to the API
Add the HTTP and internal API entry points for adding a network device
at runtime.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 17:58:06 +01:00
Rob Bradford
f664cddec9 vmm: Add support for adding network devices to the VM
The persistent memory will be hotplugged via DeviceManager and saved in
the config for later use.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 17:58:06 +01:00
Rob Bradford
8f323e61d8 vmm: Add support to DeviceManager for hotplugging network devices
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 17:58:06 +01:00
Rob Bradford
42a9896fe4 vmm: device_manager: Refactor make_virtio_net_devices
Split it into a method that creates a single device which is called by
the multiple device version so this can be used when dynamically adding
a device.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 17:58:06 +01:00
Rob Bradford
9df601a1df bin, vmm: Centralise the net syntax
This will allow the syntax to be reused with cloud-hypervsor binary and
ch-remote.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 17:58:06 +01:00
Samuel Ortiz
41d7b3a387 vmm: memory_manager: Only send the GED notification for the ACPI method
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-03-25 15:54:16 +01:00
Hui Zhu
15d9ec0149 openapit: Add hotplug_method to MemoryConfig
Add hotplug_method to MemoryConfig in cloud-hypervisor.yaml.

Signed-off-by: Hui Zhu <teawater@antfin.com>
2020-03-25 15:54:16 +01:00
Hui Zhu
abbd69abee tests: Add integration test test_virtio_mem
Add integration test test_virtio_mem for virtio-mem.

Signed-off-by: Hui Zhu <teawater@antfin.com>
2020-03-25 15:54:16 +01:00
Hui Zhu
4a7a2cff8c tests: Add test for hotplug_size and hotplug_method
Add test for hotplug_size and hotplug_method.

Signed-off-by: Hui Zhu <teawater@antfin.com>
2020-03-25 15:54:16 +01:00
Hui Zhu
e63f98182a vmm: device: Add make_virtio_mem_devices
Add make_virtio_mem_devices to add virtio-mem to vmm.

Signed-off-by: Hui Zhu <teawater@antfin.com>
2020-03-25 15:54:16 +01:00
Hui Zhu
e6b934a56a vmm: Add support for virtio-mem
This commit adds new option hotplug_method to memory config.
It can set the hotplug method to "acpi" or "virtio-mem".

Signed-off-by: Hui Zhu <teawater@antfin.com>
2020-03-25 15:54:16 +01:00
Hui Zhu
51d102c708 vm-virtio: Add virtio-mem device
The basic idea of virtio-mem is to provide a flexible, cross-architecture
memory hot plug and hot unplug solution that avoids many limitations
imposed by existing technologies, architectures, and interfaces. More
details can be found in https://lkml.org/lkml/2019/12/12/681.

This commit add virtio-mem device.

Signed-off-by: Hui Zhu <teawater@antfin.com>
2020-03-25 15:54:16 +01:00
Rob Bradford
8d05fb86e3 tests: Add integration test for hotplugging pmem device
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 13:18:17 +01:00
Rob Bradford
0b0510108d ch-remote: Add support for hotplugging persistent memory
Call the new HTTP API for hotplugging persistent memory using the same
syntax as coldplug.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 13:18:17 +01:00
Rob Bradford
44aef8f438 docs: Update API documentation to include "vm.add-pmem"
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 13:18:17 +01:00
Rob Bradford
75878dd90a openapi: Add "vm.add-pmem" entry point
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 13:18:17 +01:00
Rob Bradford
f6f4c68fb4 vmm: Add "add-pmem" to the API
Add the HTTP and internal API entry points for adding persistent memory
at runtime.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 13:18:17 +01:00
Rob Bradford
15de30f141 vmm: Add support for adding pmem devices to the VM
The persistent memory will be hotplugged via DeviceManager and saved in
the config for later use.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 13:18:17 +01:00
Rob Bradford
f7def621dd vmm: Add support to DeviceManager for hotplugging pmem devices
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 13:18:17 +01:00
Rob Bradford
8c3ea8cd76 vmm: device_manager: Refactor make_virtio_pmem_devices
Split it into a method that creates a single device which is called by
the multiple device version so this can be used when dynamically adding
a device.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 13:18:17 +01:00
Rob Bradford
a7296bbb52 bin, vmm: Centralise the pmem syntax
This will allow the syntax to be reused with cloud-hypervisor binary and
ch-remote.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 13:18:17 +01:00
Eryu Guan
61e34331c2 virtio-fs: validate request len in fs_slave_io()
We made sure gpa is in cache range, but not the end addr of request,
which is (gpa + len). If the end addr of request is beyond dax cache
window, vmm would corrupt guest memory or crash.

Fix it by making sure end addr of request is within cache range as well.
And while we're at it, return EFAULT if the request is out of range, as
write(2)/read(2) returns EFAULT when buffer is outside accessible
address space.

Signed-off-by: Eryu Guan <eguan@linux.alibaba.com>
2020-03-25 13:12:26 +01:00
Rob Bradford
4c9d15d44c vmm: Fix copy and paste error message
vm_remove_device was copied from vm_add_device but the error message
wasn't correctly updated.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 09:35:53 +00:00
Rob Bradford
96d4f1e39e tests: Add integration test for hotplugging disk device
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 09:35:53 +00:00
Rob Bradford
05ce2dc820 ch-remote: Add support for hotplugging disks
Call the new HTTP API for hotplugging disks using the same syntax as
disk coldplug.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 09:35:53 +00:00
Rob Bradford
eec1a32d95 docs: Update API documentation to include "vm.add-disk"
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 09:35:53 +00:00
Rob Bradford
82cad99c0b openapi: Add "vm.add-disk" entry point
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 09:35:53 +00:00
Rob Bradford
f2151b2734 vmm: Add "add-disk" to the API
Add the HTTP and internal API entry points for adding disks at runtime.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 09:35:53 +00:00
Rob Bradford
164ec2b8e6 vmm: Add support for adding disks to the VM
The disk will be hotplugged via DeviceManager and saved in the config
for later use.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 09:35:53 +00:00
Rob Bradford
b3082c1984 vmm: Add support to DeviceManager for hotplugging disks
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 09:35:53 +00:00
Rob Bradford
2be703ca92 vmm: device_manager: Refactor make_virtio_block_devices
Split it into a method that creates a single device which is called by
the multiple device version so this can be used when dynamically adding
a device.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 09:35:53 +00:00
Rob Bradford
66da29d8dd bin, vmm: Centralise the disk syntax
This will allow the syntax to be reused with cloud-hypervsor binary and
ch-remote.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-25 09:35:53 +00:00
dependabot-preview[bot]
ede288789a build(deps): bump micro_http from e712d6a to e89ed14
Bumps [micro_http](https://github.com/firecracker-microvm/micro-http) from `e712d6a` to `e89ed14`.
- [Release notes](https://github.com/firecracker-microvm/micro-http/releases)
- [Commits](e712d6ae23...e89ed14707)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-25 06:43:09 +00:00
Sebastien Boeuf
c1e6d0022b ci: Add memory resizing use case to vhost-user tests
All vhost-user-blk, vhost-user-net and virtio-fs integration tests are
extended with memory resizing. The point is to validate that we can
hotplug some memory and keep these backends to work as expected.

Just a note that because memory resizing is not supported by mmio, we
limit these extensions to all non-mmio use cases.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-24 19:01:15 +00:00
Sebastien Boeuf
890582b5a2 ci: Factorize kernel command line
The same Clear Linux kernel command line was repeated again and again in
the code. That's why this commit takes care of factorizing it, which
simplifies the code but also simplify maintenance whenever we'd need to
update the block UUID.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-24 19:01:15 +00:00
Sebastien Boeuf
4de258477e ci: Fix mmio tests with direct kernel boot
The hypervisor-fw does not support virtio-blk through mmio transport
layer, therefore we can only run tests with mmio if these tests boot
directly to kernel.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-24 19:01:15 +00:00
Sebastien Boeuf
f2682463a4 ci: Factorize integration tests booting from vhost-user-blk
Remove duplicated code for all boot related tests relying on
vhost-user-blk.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-24 19:01:15 +00:00
Sebastien Boeuf
5a5b3cf23b ci: Factorize vhost-user-blk integration tests
All the non-boot related vhost-user-blk tests are factorized to remove
code duplication.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-24 19:01:15 +00:00
Sebastien Boeuf
dd8debf201 ci: Run vhost-user-blk tests for mmio builds
There's no reason mmio builds cannot support vhost-user-blk, therefore
there's no reason to skip these tests for mmio.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-24 19:01:15 +00:00
Sebastien Boeuf
0c9c72c583 ci: Unify vhost-user-blk integration tests
As a first step towards factorizing as many vhost-user-blk tests as
possible, this patch takes care of unifying their expected output.

It also makes a clear separation between the tests booting from
vhost-user-blk from the ones passing an extra volume.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-24 19:01:15 +00:00
Sebastien Boeuf
c95851f48f ci: Run vhost-user-net tests for mmio transport
No reason to skip vhost-user-net integration tests for the mmio
transport build.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-24 19:01:15 +00:00
Sebastien Boeuf
68293fc0e8 ci: Factorize vhost-user-net one step further
In order to limit code duplication, vhost-user-net integration tests are
further factorized, as they now include the self spawning test.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-24 19:01:15 +00:00
Sebastien Boeuf
d75e7456fc vm-virtio: vhost-user: Send memory update to the backend
In order to keep vhost-user backend to work across guest memory resizing
happening when memory is hot-plugged or hot-unplugged, both blk, net and
fs devices are implementing the notifier to let the backend know.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-24 19:01:15 +00:00
Sebastien Boeuf
7ff82af4b2 vm-virtio: vhost-user: Factorize SET_MEM_TABLE setup
By factorizing the setup of the memory table for vhost-user, we
anticipate the fact that vhost-user devices are going to reuse this
function when the guest memory will be updated.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-24 19:01:15 +00:00
Sebastien Boeuf
e54f8ec8a5 vmm: Update memory through DeviceManager
Whenever the VM memory is resized, DeviceManager needs to be notified
so that it can subsequently notify each virtio devices about it.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-24 19:01:15 +00:00
Sebastien Boeuf
bc874a9b6f vm-virtio: Add update_memory() to VirtioDevice trait
The virtio devices backed by a vhost-user backend must send an update to
the associated backend with the new file descriptors corresponding to
the memory regions.

This patch allows such devices to be notified when such update needs to
happen.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-24 19:01:15 +00:00
dependabot-preview[bot]
93becca82a build(deps): bump backtrace from 0.3.45 to 0.3.46
Bumps [backtrace](https://github.com/rust-lang/backtrace-rs) from 0.3.45 to 0.3.46.
- [Release notes](https://github.com/rust-lang/backtrace-rs/releases)
- [Commits](https://github.com/rust-lang/backtrace-rs/compare/0.3.45...0.3.46)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-24 16:49:27 +00:00
Sebastien Boeuf
feb8d7ae90 vmm: Separate seccomp filters between VMM and API threads
This separates the filters used between the VMM and API threads, so that
we can apply different rules for each thread.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-24 14:59:57 +01:00
Sebastien Boeuf
5120c275a2 main: Add seccomp support
This change introduces a new CLI option --seccomp. This allows the user
to enable/disable the seccomp filters when needed. Because the user now
has the possibility to disable the seccomp filters, and because the
Cloud-Hypervisor project wants to enforce the maximum security by
default, the seccomp filters are now applied by default.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-24 14:59:57 +01:00
Sebastien Boeuf
f1a23d712f vmm: api: Add seccomp to the HTTP API thread
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-24 14:59:57 +01:00
Sebastien Boeuf
db62cb3f4d vmm: Add seccomp filter to the VMM thread
This commit introduces the application of the seccomp filter to the VMM
thread. The filter is empty for now (SeccompLevel::None).

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-24 14:59:57 +01:00
Sebastien Boeuf
cb98d90097 vmm: Create new seccomp_filter module
Based on the seccomp crate, we create a new vmm module responsible for
creating a seccomp filter that will be applied to the VMM main thread.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-24 14:59:57 +01:00
Sebastien Boeuf
708f02dc26 vmm: Pull seccomp crate from Firecracker
The seccomp crate from Firecracker is nicely implemented, documented and
tested, which is a good reason for relying on it to create and apply
seccomp filters.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-03-24 14:59:57 +01:00
Eryu Guan
18fbd303ab vhost-user-fs: return correct result of fs_slave_io()
Virtio-fs daemon expects fs_slave_io() returns the number of bytes
read/written on success, but we always return 0 and make userspace think
nothing has been read/written.

Fix it by returning the actual bytes read/written. Note that This
depends on the corresponding fix in vhost crate.

Fixes: #949
Signed-off-by: Eryu Guan <eguan@linux.alibaba.com>
2020-03-24 14:55:56 +01:00
Samuel Ortiz
bbc385c3f3 devices: ioapic: Remove unused MsiMessage structure
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-03-24 12:53:46 +00:00
Samuel Ortiz
2fc86ffea6 dev_cli: Always pull the latest container image
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-03-23 19:54:40 +01:00
Samuel Ortiz
4b462a54ee Dockerfile: Add cpio and bsdtar to the container image
To be able to build initrd images.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-03-23 19:54:40 +01:00
Rob Bradford
8acc15a63c build: Bump vm-memory and linux-loader dependencies
linux-loader depends on vm-memory so must be updated at the same time.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-23 14:27:41 +00:00
dependabot-preview[bot]
38ed560d3a build(deps): bump thiserror from 1.0.12 to 1.0.13
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.12 to 1.0.13.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.12...1.0.13)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-23 07:12:27 +00:00
dependabot-preview[bot]
9f67de4735 build(deps): bump proc-macro-hack from 0.5.12 to 0.5.14
Bumps [proc-macro-hack](https://github.com/dtolnay/proc-macro-hack) from 0.5.12 to 0.5.14.
- [Release notes](https://github.com/dtolnay/proc-macro-hack/releases)
- [Commits](https://github.com/dtolnay/proc-macro-hack/compare/0.5.12...0.5.14)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-22 18:40:56 +00:00
dependabot-preview[bot]
ebab8090ee build(deps): bump thiserror from 1.0.11 to 1.0.12
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.11 to 1.0.12.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.11...1.0.12)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-21 08:38:25 +00:00
dependabot-preview[bot]
c67e407620 build(deps): bump syn from 1.0.16 to 1.0.17
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.16 to 1.0.17.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.16...1.0.17)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-21 08:38:11 +00:00
Rob Bradford
bdcfe1efbd tests: Add "discard_writes" pmem test
Factorise test_virtio_pmem to test changes are not persisted if file is
readonly.

As virtio-mem is now supported in the upstream kernel we can switch to
using the firmware.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-20 14:46:34 +01:00
Rob Bradford
70986022d8 tests: Make the test_virtio_pmem test use a temporary file
Rather than using a raw OS disk image. This will be useful when the test
is extended to doing I/O on the image.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-20 14:46:34 +01:00
Rob Bradford
f7197e8415 vmm: Add a "discard_writes=" to --pmem
This opens the backing file read-only, makes the pages in the mmap()
read-only and also makes the KVM mapping read-only. The file is also
mapped with MAP_PRIVATE to make the changes local to this process only.

This is functional alternative to having support for making a
virtio-pmem device readonly. Unfortunately there is no concept of
readonly virtio-pmem (or any type of NVDIMM/PMEM) in the Linux kernel so
to be able to have a block device that is appears readonly in the guest
requires significant specification and kernel changes.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-20 14:46:34 +01:00
Rob Bradford
d11a67b0fe vmm: Use more generic MmapRegion constructor
Switch to MmapRegion::build() and fill in the fields appropriately.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-20 14:46:34 +01:00
Rob Bradford
7257e890ef vmm: Add "readonly" parameter MemoryManager::create_userspace_mapping
Use this boolean to turn on the KVM_MEM_READONLY flag to indicate that
this memory mapping should not be writable by the VM.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-03-20 14:46:34 +01:00
132 changed files with 18517 additions and 5757 deletions

View File

@@ -1,4 +1,4 @@
name: Cloud Hypervisor Actions name: Cloud Hypervisor Build
on: [pull_request, create] on: [pull_request, create]
jobs: jobs:
@@ -12,6 +12,9 @@ jobs:
- stable - stable
- beta - beta
- nightly - nightly
target:
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
steps: steps:
- name: Code checkout - name: Code checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
@@ -19,52 +22,8 @@ jobs:
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
toolchain: ${{ matrix.rust }} toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true override: true
- name: Build - name: Build
run: cargo build --release run: cargo build --release --target=${{ matrix.target }}
release:
if: github.event_name == 'create' && github.event.ref_type == 'tag'
name: Release
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@v2
- name: Install Rust toolchain (stable)
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Build
run: cargo build --release
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
draft: true
prerelease: true
- name: Upload cloud-hypervisor
id: upload-release-cloud-hypervisor
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: target/release/cloud-hypervisor
asset_name: cloud-hypervisor
asset_content_type: application/octet-stream
- name: Upload ch-remote
id: upload-release-ch-remote
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: target/release/ch-remote
asset_name: ch-remote
asset_content_type: application/octet-stream

34
.github/workflows/cross-build.yaml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: Cloud Hypervisor Cross Build
on: [pull_request, create]
jobs:
build:
if: github.event_name == 'pull_request'
name: Build
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- stable
target:
- aarch64-unknown-linux-gnu
- aarch64-unknown-linux-musl
steps:
- name: Code checkout
uses: actions/checkout@v2
- name: Install Rust toolchain (${{ matrix.rust }})
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
- name: Install arm64 libfdt
run: wget http://ftp.us.debian.org/debian/pool/main/d/device-tree-compiler/libfdt-dev_1.6.0-1_arm64.deb && dpkg-deb -xv libfdt-dev_1.6.0-1_arm64.deb ./tlibfdtdev && sudo mkdir /tmmmp && mkdir target && mkdir target/debug && mkdir target/debug/deps && sudo cp ./tlibfdtdev/usr/lib/aarch64-linux-gnu/libfdt.a target/debug/deps/libfdt.a && echo "libfdt installed"
- name: Disable "with-serde" in kvm-bindings
run: sed -i 's/"with-serde",\ //g' vmm/Cargo.toml
- name: Build
uses: actions-rs/cargo@v1
with:
use-cross: true
command: build
args: --target=${{ matrix.target }} --no-default-features --features "mmio"

67
.github/workflows/release.yaml vendored Normal file
View File

@@ -0,0 +1,67 @@
name: Cloud Hypervisor Release
on: [create]
jobs:
release:
if: github.event_name == 'create' && github.event.ref_type == 'tag'
name: Release
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@v2
- name: Install Rust toolchain (x86_64-unknown-linux-gnu)
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: x86_64-unknown-linux-gnu
- name: Install Rust toolchain (x86_64-unknown-linux-musl)
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: x86_64-unknown-linux-musl
- name: Build
run: cargo build --release --target=x86_64-unknown-linux-gnu
- name: Static Build
run: cargo build --release --target=x86_64-unknown-linux-musl
- name: Strip cloud-hypervisor binaries
run: strip target/*/release/cloud-hypervisor
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
draft: true
prerelease: true
- name: Upload cloud-hypervisor
id: upload-release-cloud-hypervisor
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: target/x86_64-unknown-linux-gnu/release/cloud-hypervisor
asset_name: cloud-hypervisor
asset_content_type: application/octet-stream
- name: Upload static cloud-hypervisor
id: upload-release-static-cloud-hypervisor
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: target/x86_64-unknown-linux-musl/release/cloud-hypervisor
asset_name: cloud-hypervisor-static
asset_content_type: application/octet-stream
- name: Upload ch-remote
id: upload-release-ch-remote
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: target/x86_64-unknown-linux-gnu/release/ch-remote
asset_name: ch-remote
asset_content_type: application/octet-stream

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
/build
/target /target
**/*.rs.bk **/*.rs.bk
**/Cargo.lock **/Cargo.lock

1397
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "cloud-hypervisor" name = "cloud-hypervisor"
version = "0.6.0" version = "0.8.0"
authors = ["The Cloud Hypervisor Authors"] authors = ["The Cloud Hypervisor Authors"]
edition = "2018" edition = "2018"
default-run = "cloud-hypervisor" default-run = "cloud-hypervisor"
@@ -8,31 +8,38 @@ build = "build.rs"
[dependencies] [dependencies]
arc-swap = ">=0.4.4" arc-swap = ">=0.4.4"
clap = { version = "2.33.0", features=["wrap_help"] } clap = { version = "2.33.1", features=["wrap_help"] }
epoll = ">=4.0.1" epoll = ">=4.0.1"
futures = { version = "0.3.4", features = ["thread-pool"] } futures = { version = "0.3.5", features = ["thread-pool"] }
lazy_static = "1.4.0" lazy_static = "1.4.0"
libc = "0.2.68" libc = "0.2.71"
log = { version = "0.4.8", features = ["std"] } log = { version = "0.4.8", features = ["std"] }
seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v0.21.1" }
serde_json = ">=1.0.9" serde_json = ">=1.0.9"
vhost_user_backend = { path = "vhost_user_backend"} vhost_user_backend = { path = "vhost_user_backend"}
vhost_user_block = { path = "vhost_user_block"} vhost_user_block = { path = "vhost_user_block"}
vhost_user_fs = { path = "vhost_user_fs"} vhost_user_fs = { path = "vhost_user_fs"}
vhost_user_net = { path = "vhost_user_net"} vhost_user_net = { path = "vhost_user_net"}
virtio-bindings = "0.1.0" virtio-bindings = { version = "0.1", features = ["virtio-v5_0_0"]}
vmm = { path = "vmm" } vmm = { path = "vmm" }
vm-device = { path = "vm-device" } vm-device = { path = "vm-device" }
vm-memory = "0.1.0" vm-memory = "0.2.1"
vmm-sys-util = "0.4.0" vmm-sys-util = "0.6.1"
vm-virtio = { path = "vm-virtio" } vm-virtio = { path = "vm-virtio" }
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] } vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
[patch.crates-io]
vm-memory = { git = "https://github.com/cloud-hypervisor/vm-memory", branch = "ch" }
[dev-dependencies] [dev-dependencies]
ssh2 = "0.8.0" ssh2 = "0.8.1"
dirs = "2.0.2" dirs = "2.0.2"
credibility = "0.1.3" credibility = "0.1.3"
tempdir= "0.3.7" tempdir= "0.3.7"
lazy_static= "1.4.0" lazy_static= "1.4.0"
tempfile = "3.1.0"
serde_json = ">=1.0.9"
net_util = { path = "net_util" }
[features] [features]
default = ["acpi", "pci", "cmos"] default = ["acpi", "pci", "cmos"]
@@ -40,6 +47,7 @@ acpi = ["vmm/acpi"]
pci = ["vmm/pci_support"] pci = ["vmm/pci_support"]
mmio = ["vmm/mmio_support"] mmio = ["vmm/mmio_support"]
cmos = ["vmm/cmos"] cmos = ["vmm/cmos"]
fwdebug = ["vmm/fwdebug"]
# Integration tests require a special environment to run in # Integration tests require a special environment to run in
integration_tests = [] integration_tests = []
@@ -53,11 +61,11 @@ members = [
"vmm", "vmm",
"vm-virtio", "vm-virtio",
"vm-device", "vm-device",
"vm-migration",
"vhost_user_block", "vhost_user_block",
"vhost_user_backend", "vhost_user_backend",
"vhost_user_fs", "vhost_user_fs",
"vhost_user_net", "vhost_user_net",
"vfio",
"net_util", "net_util",
"acpi_tables", "acpi_tables",
"arch_gen", "arch_gen",

23
Jenkinsfile vendored
View File

@@ -67,6 +67,29 @@ pipeline{
} }
} }
} }
stage ('Worker build (musl)') {
agent { node { label 'bionic' } }
options {
timeout(time: 1, unit: 'HOURS')
}
stages {
stage ('Checkout') {
steps {
checkout scm
}
}
stage ('Run unit tests for musl') {
steps {
sh "scripts/dev_cli.sh tests --unit --libc musl"
}
}
stage ('Run integration tests for musl') {
steps {
sh "scripts/dev_cli.sh tests --integration --libc musl"
}
}
}
}
} }
} }
} }

View File

@@ -229,10 +229,10 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
`cloud-hypervisor` is in a very early, pre-alpha stage. Use at your own risk! `cloud-hypervisor` is in a very early, pre-alpha stage. Use at your own risk!
As of 2019-12-12, the following cloud images are supported: As of 2020-04-23, the following cloud images are supported:
* [Clear Linux](https://download.clearlinux.org/current/) (cloudguest and kvm) * [Clear Linux](https://download.clearlinux.org/current/) (cloudguest and kvm)
* [Ubuntu Bionic](https://cloud-images.ubuntu.com/bionic/current/) (cloudimg) * [Ubuntu Bionic](https://cloud-images.ubuntu.com/bionic/current/) (cloudimg)
* [Ubuntu Eoan](https://cloud-images.ubuntu.com/eoan/current/) (cloudimg) * [Ubuntu Focal](https://cloud-images.ubuntu.com/focal/current/) (cloudimg)
Direct kernel boot to userspace should work with most rootfs. Direct kernel boot to userspace should work with most rootfs.

View File

@@ -5,5 +5,5 @@ authors = ["The Cloud Hypervisor Authors"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
vm-memory = "0.1.0" vm-memory = "0.2.1"

View File

@@ -5,13 +5,14 @@ authors = ["The Chromium OS Authors"]
[features] [features]
default = [] default = []
acpi = ["acpi_tables"]
[dependencies] [dependencies]
byteorder = "1.3.4" byteorder = "1.3.4"
kvm-bindings = "0.2.0" kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch" }
kvm-ioctls = "0.5.0" kvm-ioctls = { git = "https://github.com/cloud-hypervisor/kvm-ioctls", branch = "ch" }
libc = "0.2.68" libc = "0.2.71"
vm-memory = { version = "0.1.0", features = ["backend-mmap"] } vm-memory = { version = "0.2.1", features = ["backend-mmap"] }
acpi_tables = { path = "../acpi_tables", optional = true } acpi_tables = { path = "../acpi_tables", optional = true }
arch_gen = { path = "../arch_gen" } arch_gen = { path = "../arch_gen" }

610
arch/src/aarch64/fdt.rs Normal file
View File

@@ -0,0 +1,610 @@
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the THIRD-PARTY file.
use libc::{c_char, c_int, c_void};
use std::collections::HashMap;
use std::ffi::{CStr, CString, NulError};
use std::fmt::Debug;
use std::ptr::null;
use std::{io, result};
use super::super::DeviceType;
use super::super::InitramfsConfig;
use super::get_fdt_addr;
use super::gic::GICDevice;
use super::layout::FDT_MAX_SIZE;
use crate::aarch64::fdt::Error::CstringFDTTransform;
use vm_memory::{Address, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap};
// This is a value for uniquely identifying the FDT node declaring the interrupt controller.
const GIC_PHANDLE: u32 = 1;
// This is a value for uniquely identifying the FDT node containing the clock definition.
const CLOCK_PHANDLE: u32 = 2;
// Read the documentation specified when appending the root node to the FDT.
const ADDRESS_CELLS: u32 = 0x2;
const SIZE_CELLS: u32 = 0x2;
// As per kvm tool and
// https://www.kernel.org/doc/Documentation/devicetree/bindings/interrupt-controller/arm%2Cgic.txt
// Look for "The 1st cell..."
const GIC_FDT_IRQ_TYPE_SPI: u32 = 0;
const GIC_FDT_IRQ_TYPE_PPI: u32 = 1;
// From https://elixir.bootlin.com/linux/v4.9.62/source/include/dt-bindings/interrupt-controller/irq.h#L17
const IRQ_TYPE_EDGE_RISING: u32 = 1;
const IRQ_TYPE_LEVEL_HI: u32 = 4;
// This links to libfdt which handles the creation of the binary blob
// flattened device tree (fdt) that is passed to the kernel and indicates
// the hardware configuration of the machine.
#[link(name = "fdt")]
extern "C" {
fn fdt_create(buf: *mut c_void, bufsize: c_int) -> c_int;
fn fdt_finish_reservemap(fdt: *mut c_void) -> c_int;
fn fdt_begin_node(fdt: *mut c_void, name: *const c_char) -> c_int;
fn fdt_property(fdt: *mut c_void, name: *const c_char, val: *const c_void, len: c_int)
-> c_int;
fn fdt_end_node(fdt: *mut c_void) -> c_int;
fn fdt_open_into(fdt: *const c_void, buf: *mut c_void, bufsize: c_int) -> c_int;
fn fdt_finish(fdt: *const c_void) -> c_int;
fn fdt_pack(fdt: *mut c_void) -> c_int;
}
/// Trait for devices to be added to the Flattened Device Tree.
pub trait DeviceInfoForFDT {
/// Returns the address where this device will be loaded.
fn addr(&self) -> u64;
/// Returns the associated interrupt for this device.
fn irq(&self) -> u32;
/// Returns the amount of memory that needs to be reserved for this device.
fn length(&self) -> u64;
}
/// Errors thrown while configuring the Flattened Device Tree for aarch64.
#[derive(Debug)]
pub enum Error {
/// Failed to append node to the FDT.
AppendFDTNode(io::Error),
/// Failed to append a property to the FDT.
AppendFDTProperty(io::Error),
/// Syscall for creating FDT failed.
CreateFDT(io::Error),
/// Failed to obtain a C style string.
CstringFDTTransform(NulError),
/// Failure in calling syscall for terminating this FDT.
FinishFDTReserveMap(io::Error),
/// Failure in writing FDT in memory.
WriteFDTToMemory(GuestMemoryError),
}
type Result<T> = result::Result<T, Error>;
/// Creates the flattened device tree for this aarch64 VM.
pub fn create_fdt<T: DeviceInfoForFDT + Clone + Debug>(
guest_mem: &GuestMemoryMmap,
cmdline: &CStr,
vcpu_mpidr: Vec<u64>,
device_info: &HashMap<(DeviceType, String), T>,
gic_device: &Box<dyn GICDevice>,
initrd: &Option<InitramfsConfig>,
) -> Result<Vec<u8>> {
// Alocate stuff necessary for the holding the blob.
let mut fdt = vec![0; FDT_MAX_SIZE];
allocate_fdt(&mut fdt)?;
// For an explanation why these nodes were introduced in the blob take a look at
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/booting-without-of.txt#L845
// Look for "Required nodes and properties".
// Header or the root node as per above mentioned documentation.
append_begin_node(&mut fdt, "")?;
append_property_string(&mut fdt, "compatible", "linux,dummy-virt")?;
// For info on #address-cells and size-cells read "Note about cells and address representation"
// from the above mentioned txt file.
append_property_u32(&mut fdt, "#address-cells", ADDRESS_CELLS)?;
append_property_u32(&mut fdt, "#size-cells", SIZE_CELLS)?;
// This is not mandatory but we use it to point the root node to the node
// containing description of the interrupt controller for this VM.
append_property_u32(&mut fdt, "interrupt-parent", GIC_PHANDLE)?;
create_cpu_nodes(&mut fdt, &vcpu_mpidr)?;
create_memory_node(&mut fdt, guest_mem)?;
create_chosen_node(&mut fdt, cmdline, initrd)?;
create_gic_node(&mut fdt, gic_device)?;
create_timer_node(&mut fdt)?;
create_clock_node(&mut fdt)?;
create_psci_node(&mut fdt)?;
create_devices_node(&mut fdt, device_info)?;
// End Header node.
append_end_node(&mut fdt)?;
// Allocate another buffer so we can format and then write fdt to guest.
let mut fdt_final = vec![0; FDT_MAX_SIZE];
finish_fdt(&mut fdt, &mut fdt_final)?;
// Write FDT to memory.
let fdt_address = GuestAddress(get_fdt_addr(&guest_mem));
guest_mem
.write_slice(fdt_final.as_slice(), fdt_address)
.map_err(Error::WriteFDTToMemory)?;
Ok(fdt_final)
}
// Following are auxiliary functions for allocating and finishing the FDT.
fn allocate_fdt(fdt: &mut Vec<u8>) -> Result<()> {
// Safe since we allocated this array with FDT_MAX_SIZE.
let mut fdt_ret = unsafe { fdt_create(fdt.as_mut_ptr() as *mut c_void, FDT_MAX_SIZE as c_int) };
if fdt_ret != 0 {
return Err(Error::CreateFDT(io::Error::last_os_error()));
}
// The flattened device trees created with fdt_create() contains a list of
// reserved memory areas. We need to call `fdt_finish_reservemap` so as to make sure that there is a
// terminator in the reservemap list and whatever happened to be at the
// start of the FDT data section would end up being interpreted as
// reservemap entries.
// Safe since we previously allocated this array.
fdt_ret = unsafe { fdt_finish_reservemap(fdt.as_mut_ptr() as *mut c_void) };
if fdt_ret != 0 {
return Err(Error::FinishFDTReserveMap(io::Error::last_os_error()));
}
Ok(())
}
fn finish_fdt(from_fdt: &mut Vec<u8>, to_fdt: &mut Vec<u8>) -> Result<()> {
// Safe since we allocated `fdt_final` and previously passed in its size.
let mut fdt_ret = unsafe { fdt_finish(from_fdt.as_mut_ptr() as *mut c_void) };
if fdt_ret != 0 {
return Err(Error::FinishFDTReserveMap(io::Error::last_os_error()));
}
// Safe because we allocated both arrays with the correct size.
fdt_ret = unsafe {
fdt_open_into(
from_fdt.as_mut_ptr() as *mut c_void,
to_fdt.as_mut_ptr() as *mut c_void,
FDT_MAX_SIZE as i32,
)
};
if fdt_ret != 0 {
return Err(Error::FinishFDTReserveMap(io::Error::last_os_error()));
}
// Safe since we allocated `to_fdt`.
fdt_ret = unsafe { fdt_pack(to_fdt.as_mut_ptr() as *mut c_void) };
if fdt_ret != 0 {
return Err(Error::FinishFDTReserveMap(io::Error::last_os_error()));
}
Ok(())
}
// Following are auxiliary functions for appending nodes to FDT.
fn append_begin_node(fdt: &mut Vec<u8>, name: &str) -> Result<()> {
let cstr_name = CString::new(name).map_err(CstringFDTTransform)?;
// Safe because we allocated fdt and converted name to a CString
let fdt_ret = unsafe { fdt_begin_node(fdt.as_mut_ptr() as *mut c_void, cstr_name.as_ptr()) };
if fdt_ret != 0 {
return Err(Error::AppendFDTNode(io::Error::last_os_error()));
}
Ok(())
}
fn append_end_node(fdt: &mut Vec<u8>) -> Result<()> {
// Safe because we allocated fdt.
let fdt_ret = unsafe { fdt_end_node(fdt.as_mut_ptr() as *mut c_void) };
if fdt_ret != 0 {
return Err(Error::AppendFDTNode(io::Error::last_os_error()));
}
Ok(())
}
// Following are auxiliary functions for appending property nodes to the nodes of the FDT.
fn append_property_u32(fdt: &mut Vec<u8>, name: &str, val: u32) -> Result<()> {
append_property(fdt, name, &to_be32(val))
}
fn append_property_u64(fdt: &mut Vec<u8>, name: &str, val: u64) -> Result<()> {
append_property(fdt, name, &to_be64(val))
}
fn append_property_string(fdt: &mut Vec<u8>, name: &str, value: &str) -> Result<()> {
let cstr_value = CString::new(value).map_err(CstringFDTTransform)?;
append_property_cstring(fdt, name, &cstr_value)
}
fn append_property_cstring(fdt: &mut Vec<u8>, name: &str, cstr_value: &CStr) -> Result<()> {
let value_bytes = cstr_value.to_bytes_with_nul();
let cstr_name = CString::new(name).map_err(CstringFDTTransform)?;
// Safe because we allocated fdt, converted name and value to CStrings
let fdt_ret = unsafe {
fdt_property(
fdt.as_mut_ptr() as *mut c_void,
cstr_name.as_ptr(),
value_bytes.as_ptr() as *mut c_void,
value_bytes.len() as i32,
)
};
if fdt_ret != 0 {
return Err(Error::AppendFDTProperty(io::Error::last_os_error()));
}
Ok(())
}
fn append_property_null(fdt: &mut Vec<u8>, name: &str) -> Result<()> {
let cstr_name = CString::new(name).map_err(CstringFDTTransform)?;
// Safe because we allocated fdt, converted name to a CString
let fdt_ret = unsafe {
fdt_property(
fdt.as_mut_ptr() as *mut c_void,
cstr_name.as_ptr(),
null(),
0,
)
};
if fdt_ret != 0 {
return Err(Error::AppendFDTProperty(io::Error::last_os_error()));
}
Ok(())
}
fn append_property(fdt: &mut Vec<u8>, name: &str, val: &[u8]) -> Result<()> {
let cstr_name = CString::new(name).map_err(CstringFDTTransform)?;
let val_ptr = val.as_ptr() as *const c_void;
// Safe because we allocated fdt and converted name to a CString
let fdt_ret = unsafe {
fdt_property(
fdt.as_mut_ptr() as *mut c_void,
cstr_name.as_ptr(),
val_ptr,
val.len() as i32,
)
};
if fdt_ret != 0 {
return Err(Error::AppendFDTProperty(io::Error::last_os_error()));
}
Ok(())
}
// Auxiliary functions for writing u32/u64 numbers in big endian order.
fn to_be32(input: u32) -> [u8; 4] {
u32::to_be_bytes(input)
}
fn to_be64(input: u64) -> [u8; 8] {
u64::to_be_bytes(input)
}
// Helper functions for generating a properly formatted byte vector using 32-bit/64-bit cells.
fn generate_prop32(cells: &[u32]) -> Vec<u8> {
let mut ret: Vec<u8> = Vec::new();
for &e in cells {
ret.extend(to_be32(e).iter());
}
ret
}
fn generate_prop64(cells: &[u64]) -> Vec<u8> {
let mut ret: Vec<u8> = Vec::new();
for &e in cells {
ret.extend(to_be64(e).iter());
}
ret
}
// Following are the auxiliary function for creating the different nodes that we append to our FDT.
fn create_cpu_nodes(fdt: &mut Vec<u8>, vcpu_mpidr: &Vec<u64>) -> Result<()> {
// See https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/arm/cpus.yaml.
append_begin_node(fdt, "cpus")?;
// As per documentation, on ARM v8 64-bit systems value should be set to 2.
append_property_u32(fdt, "#address-cells", 0x02)?;
append_property_u32(fdt, "#size-cells", 0x0)?;
let num_cpus = vcpu_mpidr.len();
for cpu_index in 0..num_cpus {
let cpu_name = format!("cpu@{:x}", cpu_index);
append_begin_node(fdt, &cpu_name)?;
append_property_string(fdt, "device_type", "cpu")?;
append_property_string(fdt, "compatible", "arm,arm-v8")?;
if num_cpus > 1 {
// This is required on armv8 64-bit. See aforementioned documentation.
append_property_string(fdt, "enable-method", "psci")?;
}
// Set the field to first 24 bits of the MPIDR - Multiprocessor Affinity Register.
// See http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0488c/BABHBJCI.html.
append_property_u64(fdt, "reg", vcpu_mpidr[cpu_index] & 0x7FFFFF)?;
append_end_node(fdt)?;
}
append_end_node(fdt)?;
Ok(())
}
fn create_memory_node(fdt: &mut Vec<u8>, guest_mem: &GuestMemoryMmap) -> Result<()> {
let mem_size = guest_mem.last_addr().raw_value() - super::layout::RAM_64BIT_START + 1;
// See https://github.com/torvalds/linux/blob/master/Documentation/devicetree/booting-without-of.txt#L960
// for an explanation of this.
let mem_reg_prop = generate_prop64(&[super::layout::RAM_64BIT_START as u64, mem_size as u64]);
append_begin_node(fdt, "memory")?;
append_property_string(fdt, "device_type", "memory")?;
append_property(fdt, "reg", &mem_reg_prop)?;
append_end_node(fdt)?;
Ok(())
}
fn create_chosen_node(
fdt: &mut Vec<u8>,
cmdline: &CStr,
initrd: &Option<InitramfsConfig>,
) -> Result<()> {
append_begin_node(fdt, "chosen")?;
append_property_cstring(fdt, "bootargs", cmdline)?;
if let Some(initrd_config) = initrd {
append_property_u64(
fdt,
"linux,initrd-start",
initrd_config.address.raw_value() as u64,
)?;
append_property_u64(
fdt,
"linux,initrd-end",
initrd_config.address.raw_value() + initrd_config.size as u64,
)?;
}
append_end_node(fdt)?;
Ok(())
}
fn create_gic_node(fdt: &mut Vec<u8>, gic_device: &Box<dyn GICDevice>) -> Result<()> {
let gic_reg_prop = generate_prop64(gic_device.device_properties());
append_begin_node(fdt, "intc")?;
append_property_string(fdt, "compatible", gic_device.fdt_compatibility())?;
append_property_null(fdt, "interrupt-controller")?;
// "interrupt-cells" field specifies the number of cells needed to encode an
// interrupt source. The type shall be a <u32> and the value shall be 3 if no PPI affinity description
// is required.
append_property_u32(fdt, "#interrupt-cells", 3)?;
append_property(fdt, "reg", &gic_reg_prop)?;
append_property_u32(fdt, "phandle", GIC_PHANDLE)?;
append_property_u32(fdt, "#address-cells", 2)?;
append_property_u32(fdt, "#size-cells", 2)?;
append_property_null(fdt, "ranges")?;
let gic_intr = [
GIC_FDT_IRQ_TYPE_PPI,
gic_device.fdt_maint_irq(),
IRQ_TYPE_LEVEL_HI,
];
let gic_intr_prop = generate_prop32(&gic_intr);
append_property(fdt, "interrupts", &gic_intr_prop)?;
append_end_node(fdt)?;
Ok(())
}
fn create_clock_node(fdt: &mut Vec<u8>) -> Result<()> {
// The Advanced Peripheral Bus (APB) is part of the Advanced Microcontroller Bus Architecture
// (AMBA) protocol family. It defines a low-cost interface that is optimized for minimal power
// consumption and reduced interface complexity.
// PCLK is the clock source and this node defines exactly the clock for the APB.
append_begin_node(fdt, "apb-pclk")?;
append_property_string(fdt, "compatible", "fixed-clock")?;
append_property_u32(fdt, "#clock-cells", 0x0)?;
append_property_u32(fdt, "clock-frequency", 24000000)?;
append_property_string(fdt, "clock-output-names", "clk24mhz")?;
append_property_u32(fdt, "phandle", CLOCK_PHANDLE)?;
append_end_node(fdt)?;
Ok(())
}
fn create_timer_node(fdt: &mut Vec<u8>) -> Result<()> {
// See
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/interrupt-controller/arch_timer.txt
// These are fixed interrupt numbers for the timer device.
let irqs = [13, 14, 11, 10];
let compatible = "arm,armv8-timer";
let mut timer_reg_cells: Vec<u32> = Vec::new();
for &irq in irqs.iter() {
timer_reg_cells.push(GIC_FDT_IRQ_TYPE_PPI);
timer_reg_cells.push(irq);
timer_reg_cells.push(IRQ_TYPE_LEVEL_HI);
}
let timer_reg_prop = generate_prop32(timer_reg_cells.as_slice());
append_begin_node(fdt, "timer")?;
append_property_string(fdt, "compatible", compatible)?;
append_property_null(fdt, "always-on")?;
append_property(fdt, "interrupts", &timer_reg_prop)?;
append_end_node(fdt)?;
Ok(())
}
fn create_psci_node(fdt: &mut Vec<u8>) -> Result<()> {
let compatible = "arm,psci-0.2";
append_begin_node(fdt, "psci")?;
append_property_string(fdt, "compatible", compatible)?;
// Two methods available: hvc and smc.
// As per documentation, PSCI calls between a guest and hypervisor may use the HVC conduit instead of SMC.
// So, since we are using kvm, we need to use hvc.
append_property_string(fdt, "method", "hvc")?;
append_end_node(fdt)?;
Ok(())
}
fn create_virtio_node<T: DeviceInfoForFDT + Clone + Debug>(
fdt: &mut Vec<u8>,
dev_info: &T,
) -> Result<()> {
let device_reg_prop = generate_prop64(&[dev_info.addr(), dev_info.length()]);
let irq = generate_prop32(&[GIC_FDT_IRQ_TYPE_SPI, dev_info.irq(), IRQ_TYPE_EDGE_RISING]);
append_begin_node(fdt, &format!("virtio_mmio@{:x}", dev_info.addr()))?;
append_property_string(fdt, "compatible", "virtio,mmio")?;
append_property(fdt, "reg", &device_reg_prop)?;
append_property(fdt, "interrupts", &irq)?;
append_property_u32(fdt, "interrupt-parent", GIC_PHANDLE)?;
append_end_node(fdt)?;
Ok(())
}
fn create_serial_node<T: DeviceInfoForFDT + Clone + Debug>(
fdt: &mut Vec<u8>,
dev_info: &T,
) -> Result<()> {
let serial_reg_prop = generate_prop64(&[dev_info.addr(), dev_info.length()]);
let irq = generate_prop32(&[GIC_FDT_IRQ_TYPE_SPI, dev_info.irq(), IRQ_TYPE_EDGE_RISING]);
append_begin_node(fdt, &format!("uart@{:x}", dev_info.addr()))?;
append_property_string(fdt, "compatible", "ns16550a")?;
append_property(fdt, "reg", &serial_reg_prop)?;
append_property_u32(fdt, "clocks", CLOCK_PHANDLE)?;
append_property_string(fdt, "clock-names", "apb_pclk")?;
append_property(fdt, "interrupts", &irq)?;
append_end_node(fdt)?;
Ok(())
}
fn create_rtc_node<T: DeviceInfoForFDT + Clone + Debug>(
fdt: &mut Vec<u8>,
dev_info: &T,
) -> Result<()> {
let compatible = b"arm,pl031\0arm,primecell\0";
let rtc_reg_prop = generate_prop64(&[dev_info.addr(), dev_info.length()]);
let irq = generate_prop32(&[GIC_FDT_IRQ_TYPE_SPI, dev_info.irq(), IRQ_TYPE_LEVEL_HI]);
append_begin_node(fdt, &format!("rtc@{:x}", dev_info.addr()))?;
append_property(fdt, "compatible", compatible)?;
append_property(fdt, "reg", &rtc_reg_prop)?;
append_property(fdt, "interrupts", &irq)?;
append_property_u32(fdt, "clocks", CLOCK_PHANDLE)?;
append_property_string(fdt, "clock-names", "apb_pclk")?;
append_end_node(fdt)?;
Ok(())
}
fn create_devices_node<T: DeviceInfoForFDT + Clone + Debug>(
fdt: &mut Vec<u8>,
dev_info: &HashMap<(DeviceType, String), T>,
) -> Result<()> {
// Create one temp Vec to store all virtio devices
let mut ordered_virtio_device: Vec<&T> = Vec::new();
for ((device_type, _device_id), info) in dev_info {
match device_type {
DeviceType::RTC => create_rtc_node(fdt, info)?,
DeviceType::Serial => create_serial_node(fdt, info)?,
DeviceType::Virtio(_) => {
ordered_virtio_device.push(info);
}
}
}
// Sort out virtio devices by address from low to high and insert them into fdt table.
ordered_virtio_device.sort_by(|a, b| a.addr().cmp(&b.addr()));
for ordered_device_info in ordered_virtio_device.drain(..) {
create_virtio_node(fdt, ordered_device_info)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::aarch64::gic::create_gic;
use crate::aarch64::layout;
use kvm_ioctls::Kvm;
const LEN: u64 = 4096;
#[derive(Clone, Debug)]
pub struct MMIODeviceInfo {
addr: u64,
irq: u32,
}
impl DeviceInfoForFDT for MMIODeviceInfo {
fn addr(&self) -> u64 {
self.addr
}
fn irq(&self) -> u32 {
self.irq
}
fn length(&self) -> u64 {
LEN
}
}
// The `load` function from the `device_tree` will mistakenly check the actual size
// of the buffer with the allocated size. This works around that.
fn set_size(buf: &mut [u8], pos: usize, val: usize) {
buf[pos] = ((val >> 24) & 0xff) as u8;
buf[pos + 1] = ((val >> 16) & 0xff) as u8;
buf[pos + 2] = ((val >> 8) & 0xff) as u8;
buf[pos + 3] = (val & 0xff) as u8;
}
#[test]
fn test_create_fdt_with_devices() {
let mut regions = Vec::new();
regions.push((
GuestAddress(layout::RAM_64BIT_START),
(layout::FDT_MAX_SIZE + 0x1000) as usize,
));
let mem = GuestMemoryMmap::from_ranges(&regions).expect("Cannot initialize memory");
let dev_info: HashMap<(DeviceType, std::string::String), MMIODeviceInfo> = [
(
(DeviceType::Serial, DeviceType::Serial.to_string()),
MMIODeviceInfo { addr: 0x00, irq: 1 },
),
(
(DeviceType::Virtio(1), "virtio".to_string()),
MMIODeviceInfo {
addr: 0x00 + LEN,
irq: 2,
},
),
(
(DeviceType::RTC, "rtc".to_string()),
MMIODeviceInfo {
addr: 0x00 + 2 * LEN,
irq: 3,
},
),
]
.iter()
.cloned()
.collect();
let kvm = Kvm::new().unwrap();
let vm = kvm.create_vm().unwrap();
let gic = create_gic(&vm, 1).unwrap();
assert!(create_fdt(
&mem,
&CString::new("console=tty0").unwrap(),
vec![0],
&dev_info,
&gic,
&None,
)
.is_ok())
}
}

159
arch/src/aarch64/gic.rs Normal file
View File

@@ -0,0 +1,159 @@
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::{boxed::Box, result};
use kvm_ioctls::{DeviceFd, VmFd};
use super::gicv2::GICv2;
use super::gicv3::GICv3;
/// Errors thrown while setting up the GIC.
#[derive(Debug)]
pub enum Error {
/// Error while calling KVM ioctl for setting up the global interrupt controller.
CreateGIC(kvm_ioctls::Error),
/// Error while setting device attributes for the GIC.
SetDeviceAttribute(kvm_ioctls::Error),
}
type Result<T> = result::Result<T, Error>;
/// Trait for GIC devices.
pub trait GICDevice: Send + Sync {
/// Returns the file descriptor of the GIC device
fn device_fd(&self) -> &DeviceFd;
/// Returns an array with GIC device properties
fn device_properties(&self) -> &[u64];
/// Returns the number of vCPUs this GIC handles
fn vcpu_count(&self) -> u64;
/// Returns the fdt compatibility property of the device
fn fdt_compatibility(&self) -> &str;
/// Returns the maint_irq fdt property of the device
fn fdt_maint_irq(&self) -> u32;
/// Returns the GIC version of the device
fn version() -> u32
where
Self: Sized;
/// Create the GIC device object
fn create_device(fd: DeviceFd, vcpu_count: u64) -> Box<dyn GICDevice>
where
Self: Sized;
/// Setup the device-specific attributes
fn init_device_attributes(gic_device: &Box<dyn GICDevice>) -> Result<()>
where
Self: Sized;
/// Initialize a GIC device
fn init_device(vm: &VmFd) -> Result<DeviceFd>
where
Self: Sized,
{
let mut gic_device = kvm_bindings::kvm_create_device {
type_: Self::version(),
fd: 0,
flags: 0,
};
vm.create_device(&mut gic_device).map_err(Error::CreateGIC)
}
/// Set a GIC device attribute
fn set_device_attribute(
fd: &DeviceFd,
group: u32,
attr: u64,
addr: u64,
flags: u32,
) -> Result<()>
where
Self: Sized,
{
let attr = kvm_bindings::kvm_device_attr {
group: group,
attr: attr,
addr: addr,
flags: flags,
};
fd.set_device_attr(&attr)
.map_err(Error::SetDeviceAttribute)?;
Ok(())
}
/// Finalize the setup of a GIC device
fn finalize_device(gic_device: &Box<dyn GICDevice>) -> Result<()>
where
Self: Sized,
{
/* We need to tell the kernel how many irqs to support with this vgic.
* See the `layout` module for details.
*/
let nr_irqs: u32 = super::layout::IRQ_MAX - super::layout::IRQ_BASE + 1;
let nr_irqs_ptr = &nr_irqs as *const u32;
Self::set_device_attribute(
gic_device.device_fd(),
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
0,
nr_irqs_ptr as u64,
0,
)?;
/* Finalize the GIC.
* See https://code.woboq.org/linux/linux/virt/kvm/arm/vgic/vgic-kvm-device.c.html#211.
*/
Self::set_device_attribute(
gic_device.device_fd(),
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL,
u64::from(kvm_bindings::KVM_DEV_ARM_VGIC_CTRL_INIT),
0,
0,
)?;
Ok(())
}
/// Method to initialize the GIC device
fn new(vm: &VmFd, vcpu_count: u64) -> Result<Box<dyn GICDevice>>
where
Self: Sized,
{
let vgic_fd = Self::init_device(vm)?;
let device = Self::create_device(vgic_fd, vcpu_count);
Self::init_device_attributes(&device)?;
Self::finalize_device(&device)?;
Ok(device)
}
}
/// Create a GIC device.
///
/// It will try to create by default a GICv3 device. If that fails it will try
/// to fall-back to a GICv2 device.
pub fn create_gic(vm: &VmFd, vcpu_count: u64) -> Result<Box<dyn GICDevice>> {
GICv3::new(vm, vcpu_count).or_else(|_| GICv2::new(vm, vcpu_count))
}
#[cfg(test)]
mod tests {
use super::*;
use kvm_ioctls::Kvm;
#[test]
fn test_create_gic() {
let kvm = Kvm::new().unwrap();
let vm = kvm.create_vm().unwrap();
assert!(create_gic(&vm, 1).is_ok());
}
}

114
arch/src/aarch64/gicv2.rs Normal file
View File

@@ -0,0 +1,114 @@
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::{boxed::Box, result};
use kvm_ioctls::DeviceFd;
use super::gic::{Error, GICDevice};
type Result<T> = result::Result<T, Error>;
/// Represent a GIC v2 device
pub struct GICv2 {
/// The file descriptor for the KVM device
fd: DeviceFd,
/// GIC device properties, to be used for setting up the fdt entry
properties: [u64; 4],
/// Number of CPUs handled by the device
vcpu_count: u64,
}
impl GICv2 {
// Unfortunately bindgen omits defines that are based on other defines.
// See arch/arm64/include/uapi/asm/kvm.h file from the linux kernel.
const KVM_VGIC_V2_DIST_SIZE: u64 = 0x1000;
const KVM_VGIC_V2_CPU_SIZE: u64 = 0x2000;
// Device trees specific constants
const ARCH_GIC_V2_MAINT_IRQ: u32 = 8;
/// Get the address of the GICv2 distributor.
const fn get_dist_addr() -> u64 {
super::layout::MAPPED_IO_START - GICv2::KVM_VGIC_V2_DIST_SIZE
}
/// Get the size of the GIC_v2 distributor.
const fn get_dist_size() -> u64 {
GICv2::KVM_VGIC_V2_DIST_SIZE
}
/// Get the address of the GIC_v2 CPU.
const fn get_cpu_addr() -> u64 {
GICv2::get_dist_addr() - GICv2::KVM_VGIC_V2_CPU_SIZE
}
/// Get the size of the GIC_v2 CPU.
const fn get_cpu_size() -> u64 {
GICv2::KVM_VGIC_V2_CPU_SIZE
}
}
impl GICDevice for GICv2 {
fn version() -> u32 {
kvm_bindings::kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V2
}
fn device_fd(&self) -> &DeviceFd {
&self.fd
}
fn device_properties(&self) -> &[u64] {
&self.properties
}
fn vcpu_count(&self) -> u64 {
self.vcpu_count
}
fn fdt_compatibility(&self) -> &str {
"arm,gic-400"
}
fn fdt_maint_irq(&self) -> u32 {
GICv2::ARCH_GIC_V2_MAINT_IRQ
}
fn create_device(fd: DeviceFd, vcpu_count: u64) -> Box<dyn GICDevice> {
Box::new(GICv2 {
fd: fd,
properties: [
GICv2::get_dist_addr(),
GICv2::get_dist_size(),
GICv2::get_cpu_addr(),
GICv2::get_cpu_size(),
],
vcpu_count: vcpu_count,
})
}
fn init_device_attributes(gic_device: &Box<dyn GICDevice>) -> Result<()> {
/* Setting up the distributor attribute.
We are placing the GIC below 1GB so we need to substract the size of the distributor. */
Self::set_device_attribute(
&gic_device.device_fd(),
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
u64::from(kvm_bindings::KVM_VGIC_V2_ADDR_TYPE_DIST),
&GICv2::get_dist_addr() as *const u64 as u64,
0,
)?;
/* Setting up the CPU attribute. */
Self::set_device_attribute(
&gic_device.device_fd(),
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
u64::from(kvm_bindings::KVM_VGIC_V2_ADDR_TYPE_CPU),
&GICv2::get_cpu_addr() as *const u64 as u64,
0,
)?;
Ok(())
}
}

117
arch/src/aarch64/gicv3.rs Normal file
View File

@@ -0,0 +1,117 @@
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::{boxed::Box, result};
use kvm_ioctls::DeviceFd;
use super::gic::{Error, GICDevice};
type Result<T> = result::Result<T, Error>;
pub struct GICv3 {
/// The file descriptor for the KVM device
fd: DeviceFd,
/// GIC device properties, to be used for setting up the fdt entry
properties: [u64; 4],
/// Number of CPUs handled by the device
vcpu_count: u64,
}
impl GICv3 {
// Unfortunately bindgen omits defines that are based on other defines.
// See arch/arm64/include/uapi/asm/kvm.h file from the linux kernel.
const SZ_64K: u64 = 0x0001_0000;
const KVM_VGIC_V3_DIST_SIZE: u64 = GICv3::SZ_64K;
const KVM_VGIC_V3_REDIST_SIZE: u64 = (2 * GICv3::SZ_64K);
// Device trees specific constants
const ARCH_GIC_V3_MAINT_IRQ: u32 = 9;
/// Get the address of the GIC distributor.
fn get_dist_addr() -> u64 {
super::layout::MAPPED_IO_START - GICv3::KVM_VGIC_V3_DIST_SIZE
}
/// Get the size of the GIC distributor.
fn get_dist_size() -> u64 {
GICv3::KVM_VGIC_V3_DIST_SIZE
}
/// Get the address of the GIC redistributors.
fn get_redists_addr(vcpu_count: u64) -> u64 {
GICv3::get_dist_addr() - GICv3::get_redists_size(vcpu_count)
}
/// Get the size of the GIC redistributors.
fn get_redists_size(vcpu_count: u64) -> u64 {
vcpu_count * GICv3::KVM_VGIC_V3_REDIST_SIZE
}
}
impl GICDevice for GICv3 {
fn version() -> u32 {
kvm_bindings::kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V3
}
fn device_fd(&self) -> &DeviceFd {
&self.fd
}
fn device_properties(&self) -> &[u64] {
&self.properties
}
fn vcpu_count(&self) -> u64 {
self.vcpu_count
}
fn fdt_compatibility(&self) -> &str {
"arm,gic-v3"
}
fn fdt_maint_irq(&self) -> u32 {
GICv3::ARCH_GIC_V3_MAINT_IRQ
}
fn create_device(fd: DeviceFd, vcpu_count: u64) -> Box<dyn GICDevice> {
Box::new(GICv3 {
fd: fd,
properties: [
GICv3::get_dist_addr(),
GICv3::get_dist_size(),
GICv3::get_redists_addr(vcpu_count),
GICv3::get_redists_size(vcpu_count),
],
vcpu_count: vcpu_count,
})
}
fn init_device_attributes(gic_device: &Box<dyn GICDevice>) -> Result<()> {
/* Setting up the distributor attribute.
We are placing the GIC below 1GB so we need to substract the size of the distributor.
*/
Self::set_device_attribute(
&gic_device.device_fd(),
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
u64::from(kvm_bindings::KVM_VGIC_V3_ADDR_TYPE_DIST),
&GICv3::get_dist_addr() as *const u64 as u64,
0,
)?;
/* Setting up the redistributors' attribute.
We are calculating here the start of the redistributors address. We have one per CPU.
*/
Self::set_device_attribute(
&gic_device.device_fd(),
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
u64::from(kvm_bindings::KVM_VGIC_V3_ADDR_TYPE_REDIST),
&GICv3::get_redists_addr(u64::from(gic_device.vcpu_count())) as *const u64 as u64,
0,
)?;
Ok(())
}
}

View File

@@ -1,7 +1,86 @@
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
/// Kernel command line start address. //
pub const CMDLINE_START: usize = 0x0; // Memory layout of Aarch64 guest:
/// Kernel command line start address maximum size. //
pub const CMDLINE_MAX_SIZE: usize = 0x0; // Physical +---------------------------------------------------------------+
// address | |
// end | |
// ~ ~ ~ ~
// | |
// | Highmem PCI MMIO space |
// | |
// RAM end +---------------------------------------------------------------+
// (dynamic, | |
// including | |
// hotplug ~ ~ ~ ~
// memory) | |
// | DRAM |
// | |
// 2GB +---------------------------------------------------------------+
// | |
// | Reserved |
// | |
// 1G+256M +---------------------------------------------------------------+
// | |
// | PCI MMCONFIG space |
// | |
// 1GB +---------------------------------------------------------------+
// | |
// | PCI MMIO space |
// | |
// 256 M +---------------------------------------------------------------|
// | |
// | Legacy devices space |
// | |
// 144 M +---------------------------------------------------------------|
// | |
// | Reserverd (now GIC is here) |
// | |
// 0GB +---------------------------------------------------------------+
//
//
use vm_memory::{GuestAddress, GuestUsize};
/// Below this address will reside the GIC, above this address will reside the MMIO devices.
pub const MAPPED_IO_START: u64 = 0x0900_0000;
/// Space 0x0900_0000 ~ 0x1000_0000 is reserved for legacy devices.
pub const LEGACY_SERIAL_MAPPED_IO_START: u64 = 0x0900_0000;
pub const LEGACY_RTC_MAPPED_IO_START: u64 = 0x0901_0000;
/// Legacy space will be allocated at once whiling setting up legacy devices.
pub const LEGACY_DEVICES_MAPPED_IO_SIZE: u64 = 0x0700_0000;
/// Starting from 0x1000_0000 (256MiB), the 768MiB (ends at 1 GiB) is used for PCIE MMIO
pub const MEM_32BIT_DEVICES_START: GuestAddress = GuestAddress(0x1000_0000);
pub const MEM_32BIT_DEVICES_SIZE: u64 = 0x3000_0000;
/// PCI MMCONFIG space (start: after the device space at 1 GiB, length: 256MiB)
pub const PCI_MMCONFIG_START: GuestAddress = GuestAddress(0x4000_0000);
pub const PCI_MMCONFIG_SIZE: GuestUsize = 256 << 20;
/// Start of RAM on 64 bit ARM.
pub const RAM_64BIT_START: u64 = 0x8000_0000;
/// Kernel command line maximum size.
/// As per `arch/arm64/include/uapi/asm/setup.h`.
pub const CMDLINE_MAX_SIZE: usize = 2048;
/// Maximum size of the device tree blob as specified in https://www.kernel.org/doc/Documentation/arm64/booting.txt.
pub const FDT_MAX_SIZE: usize = 0x20_0000;
// As per virt/kvm/arm/vgic/vgic-kvm-device.c we need
// the number of interrupts our GIC will support to be:
// * bigger than 32
// * less than 1023 and
// * a multiple of 32.
// We are setting up our interrupt controller to support a maximum of 128 interrupts.
/// First usable interrupt on aarch64.
pub const IRQ_BASE: u32 = 32;
/// Last usable interrupt on aarch64.
pub const IRQ_MAX: u32 = 159;

View File

@@ -1,27 +1,251 @@
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
/// Module for the flattened device tree.
pub mod fdt;
/// Module for the global interrupt controller configuration.
pub mod gic;
mod gicv2;
mod gicv3;
/// Layout for this aarch64 system.
pub mod layout; pub mod layout;
/// Logic for configuring aarch64 registers.
pub mod regs;
use memory_model::{GuestAddress, GuestMemory}; pub use self::fdt::DeviceInfoForFDT;
use crate::DeviceType;
use crate::RegionType;
use kvm_ioctls::*;
use std::collections::HashMap;
use std::ffi::CStr;
use std::fmt::Debug;
use vm_memory::{
Address, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, GuestMemoryMmap,
GuestUsize,
};
/// Stub function that needs to be implemented when aarch64 functionality is added. /// Errors thrown while configuring aarch64 system.
pub fn arch_memory_regions(size: usize) -> Vec<(GuestAddress, usize, RegionType)> { #[derive(Debug)]
vec![(GuestAddress(0), size, RegionType::Ram)] pub enum Error {
/// Failed to create a FDT.
SetupFDT(fdt::Error),
/// Failed to create a GIC.
SetupGIC(gic::Error),
/// Failed to compute the initrd address.
InitrdAddress,
/// Error configuring the general purpose registers
REGSConfiguration(regs::Error),
/// Error fetching prefered target
VcpuArmPreferredTarget(kvm_ioctls::Error),
/// Error doing Vcpu Init on Arm.
VcpuArmInit(kvm_ioctls::Error),
} }
/// Stub function that needs to be implemented when aarch64 functionality is added. impl From<Error> for super::Error {
pub fn configure_system( fn from(e: Error) -> super::Error {
_guest_mem: &GuestMemory, super::Error::AArch64Setup(e)
_cmdline_addr: GuestAddress, }
_cmdline_size: usize, }
_num_cpus: u8,
_rsdp_addr: Option<GuestAddress>, #[derive(Debug, Copy, Clone)]
/// Specifies the entry point address where the guest must start
/// executing code.
pub struct EntryPoint {
/// Address in guest memory where the guest must start execution
pub entry_addr: GuestAddress,
}
/// Configure the specified VCPU, and return its MPIDR.
pub fn configure_vcpu(
fd: &VcpuFd,
id: u8,
vm_fd: &VmFd,
kernel_entry_point: Option<EntryPoint>,
vm_memory: &GuestMemoryAtomic<GuestMemoryMmap>,
) -> super::Result<u64> {
let mut kvi: kvm_bindings::kvm_vcpu_init = kvm_bindings::kvm_vcpu_init::default();
// This reads back the kernel's preferred target type.
vm_fd
.get_preferred_target(&mut kvi)
.map_err(Error::VcpuArmPreferredTarget)?;
// We already checked that the capability is supported.
kvi.features[0] |= 1 << kvm_bindings::KVM_ARM_VCPU_PSCI_0_2;
// Non-boot cpus are powered off initially.
if id > 0 {
kvi.features[0] |= 1 << kvm_bindings::KVM_ARM_VCPU_POWER_OFF;
}
fd.vcpu_init(&kvi).map_err(Error::VcpuArmInit)?;
if let Some(kernel_entry_point) = kernel_entry_point {
regs::setup_regs(
fd,
id,
kernel_entry_point.entry_addr.raw_value(),
&vm_memory.memory(),
)
.map_err(Error::REGSConfiguration)?;
}
let mpidr = regs::read_mpidr(fd).map_err(Error::REGSConfiguration)?;
Ok(mpidr)
}
pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, RegionType)> {
let mut regions = Vec::new();
// 0 ~ 256 MiB: Reserved
regions.push((
GuestAddress(0),
layout::MEM_32BIT_DEVICES_START.0 as usize,
RegionType::Reserved,
));
// 256 MiB ~ 1 G: MMIO space
regions.push((
layout::MEM_32BIT_DEVICES_START,
layout::MEM_32BIT_DEVICES_SIZE as usize,
RegionType::SubRegion,
));
// 1G ~ 2G: reserved. The leading 256M for PCIe MMCONFIG space
regions.push((
layout::PCI_MMCONFIG_START,
(layout::RAM_64BIT_START - layout::PCI_MMCONFIG_START.0) as usize,
RegionType::Reserved,
));
regions.push((
GuestAddress(layout::RAM_64BIT_START),
size as usize,
RegionType::Ram,
));
regions
}
/// Configures the system and should be called once per vm before starting vcpu threads.
///
/// # Arguments
///
/// * `guest_mem` - The memory to be used by the guest.
/// * `num_cpus` - Number of virtual CPUs the guest will have.
#[allow(clippy::too_many_arguments)]
#[allow(unused_variables)]
pub fn configure_system<T: DeviceInfoForFDT + Clone + Debug>(
vm_fd: &VmFd,
guest_mem: &GuestMemoryMmap,
cmdline_cstring: &CStr,
vcpu_count: u64,
vcpu_mpidr: Vec<u64>,
device_info: &HashMap<(DeviceType, String), T>,
initrd: &Option<super::InitramfsConfig>,
) -> super::Result<()> { ) -> super::Result<()> {
let gic_device = gic::create_gic(vm_fd, vcpu_count).map_err(Error::SetupGIC)?;
let dtb = fdt::create_fdt(
guest_mem,
cmdline_cstring,
vcpu_mpidr,
device_info,
&gic_device,
initrd,
)
.map_err(Error::SetupFDT)?;
Ok(()) Ok(())
} }
/// Stub function that needs to be implemented when aarch64 functionality is added. /// Returns the memory address where the kernel could be loaded.
pub fn get_reserved_mem_addr() -> usize { pub fn get_kernel_start() -> u64 {
0 layout::RAM_64BIT_START
}
// Auxiliary function to get the address where the device tree blob is loaded.
fn get_fdt_addr(mem: &GuestMemoryMmap) -> u64 {
// If the memory allocated is smaller than the size allocated for the FDT,
// we return the start of the DRAM so that
// we allow the code to try and load the FDT.
if let Some(addr) = mem.last_addr().checked_sub(layout::FDT_MAX_SIZE as u64 - 1) {
if mem.address_in_range(addr) {
return addr.raw_value();
}
}
layout::RAM_64BIT_START
}
pub fn get_host_cpu_phys_bits() -> u8 {
// The value returned here is used to determine the physical address space size
// for a VM (IPA size).
// In recent kernel versions, the maxium IPA size supported by the host can be
// known by querying cap KVM_CAP_ARM_VM_IPA_SIZE. And the IPA size for a
// guest can be configured smaller.
// But in Cloud-Hypervisor we simply use the maxium value for the VM.
// Reference https://lwn.net/Articles/766767/.
//
// The correct way to query KVM_CAP_ARM_VM_IPA_SIZE is via rust-vmm/kvm-ioctls,
// which wraps all IOCTL's and provides easy interface to user hypervisors.
// For now the cap hasn't been supported. A separate patch will be submitted to
// rust-vmm to add it.
// So a hardcoded value is used here as a temporary solution.
// It will be replace once rust-vmm/kvm-ioctls is ready.
//
40
}
pub fn check_required_kvm_extensions(kvm: &Kvm) -> super::Result<()> {
if !kvm.check_extension(Cap::SignalMsi) {
return Err(super::Error::CapabilityMissing(Cap::SignalMsi));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_arch_memory_regions_dram() {
let regions = arch_memory_regions((1usize << 32) as u64); //4GB
assert_eq!(4, regions.len());
assert_eq!(GuestAddress(layout::RAM_64BIT_START), regions[3].0);
assert_eq!(1usize << 32, regions[3].1);
assert_eq!(RegionType::Ram, regions[3].2);
}
#[test]
fn test_get_fdt_addr() {
let mut regions = Vec::new();
regions.push((
GuestAddress(layout::RAM_64BIT_START),
(layout::FDT_MAX_SIZE - 0x1000) as usize,
));
let mem = GuestMemoryMmap::from_ranges(&regions).expect("Cannot initialize memory");
assert_eq!(get_fdt_addr(&mem), layout::RAM_64BIT_START);
regions.clear();
regions.push((
GuestAddress(layout::RAM_64BIT_START),
(layout::FDT_MAX_SIZE) as usize,
));
let mem = GuestMemoryMmap::from_ranges(&regions).expect("Cannot initialize memory");
assert_eq!(get_fdt_addr(&mem), layout::RAM_64BIT_START);
regions.clear();
regions.push((
GuestAddress(layout::RAM_64BIT_START),
(layout::FDT_MAX_SIZE + 0x1000) as usize,
));
let mem = GuestMemoryMmap::from_ranges(&regions).expect("Cannot initialize memory");
assert_eq!(get_fdt_addr(&mem), 0x1000 + layout::RAM_64BIT_START);
regions.clear();
}
} }

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

@@ -0,0 +1,198 @@
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the THIRD-PARTY file.
use std::{mem, result};
use super::get_fdt_addr;
use kvm_bindings::{
user_pt_regs, KVM_REG_ARM64, KVM_REG_ARM64_SYSREG, KVM_REG_ARM64_SYSREG_CRM_MASK,
KVM_REG_ARM64_SYSREG_CRM_SHIFT, KVM_REG_ARM64_SYSREG_CRN_MASK, KVM_REG_ARM64_SYSREG_CRN_SHIFT,
KVM_REG_ARM64_SYSREG_OP0_MASK, KVM_REG_ARM64_SYSREG_OP0_SHIFT, KVM_REG_ARM64_SYSREG_OP1_MASK,
KVM_REG_ARM64_SYSREG_OP1_SHIFT, KVM_REG_ARM64_SYSREG_OP2_MASK, KVM_REG_ARM64_SYSREG_OP2_SHIFT,
KVM_REG_ARM_CORE, KVM_REG_SIZE_U64,
};
use kvm_ioctls::VcpuFd;
use vm_memory::GuestMemoryMmap;
/// Errors thrown while setting aarch64 registers.
#[derive(Debug)]
pub enum Error {
/// Failed to set core register (PC, PSTATE or general purpose ones).
SetCoreRegister(kvm_ioctls::Error),
/// Failed to get a system register.
GetSysRegister(kvm_ioctls::Error),
}
type Result<T> = result::Result<T, Error>;
#[allow(non_upper_case_globals)]
// PSR (Processor State Register) bits.
// Taken from arch/arm64/include/uapi/asm/ptrace.h.
const PSR_MODE_EL1h: u64 = 0x0000_0005;
const PSR_F_BIT: u64 = 0x0000_0040;
const PSR_I_BIT: u64 = 0x0000_0080;
const PSR_A_BIT: u64 = 0x0000_0100;
const PSR_D_BIT: u64 = 0x0000_0200;
// Taken from arch/arm64/kvm/inject_fault.c.
const PSTATE_FAULT_BITS_64: u64 = PSR_MODE_EL1h | PSR_A_BIT | PSR_F_BIT | PSR_I_BIT | PSR_D_BIT;
// Following are macros that help with getting the ID of a aarch64 core register.
// The core register are represented by the user_pt_regs structure. Look for it in
// arch/arm64/include/uapi/asm/ptrace.h.
// This macro gets the offset of a structure (i.e `str`) member (i.e `field`) without having
// an instance of that structure.
// It uses a null pointer to retrieve the offset to the field.
// Inspired by C solution: `#define offsetof(str, f) ((size_t)(&((str *)0)->f))`.
// Doing `offset__of!(user_pt_regs, pstate)` in our rust code will trigger the following:
// unsafe { &(*(0 as *const user_pt_regs)).pstate as *const _ as usize }
// The dereference expression produces an lvalue, but that lvalue is not actually read from,
// we're just doing pointer math on it, so in theory, it should safe.
macro_rules! offset__of {
($str:ty, $field:ident) => {
unsafe { &(*(0 as *const $str)).$field as *const _ as usize }
};
}
macro_rules! arm64_core_reg {
($reg: tt) => {
// As per `kvm_arm_copy_reg_indices`, the id of a core register can be obtained like this:
// `const u64 core_reg = KVM_REG_ARM64 | KVM_REG_SIZE_U64 | KVM_REG_ARM_CORE | i`, where i is obtained with:
// `for (i = 0; i < sizeof(struct kvm_regs) / sizeof(__u32); i++) {`
// We are using here `user_pt_regs` since this structure contains the core register and it is at
// the start of `kvm_regs`.
// struct kvm_regs {
// struct user_pt_regs regs; /* sp = sp_el0 */
//
// __u64 sp_el1;
// __u64 elr_el1;
//
// __u64 spsr[KVM_NR_SPSR];
//
// struct user_fpsimd_state fp_regs;
//};
// struct user_pt_regs {
// __u64 regs[31];
// __u64 sp;
// __u64 pc;
// __u64 pstate;
//};
// In our implementation we need: pc, pstate and user_pt_regs->regs[0].
KVM_REG_ARM64 as u64
| KVM_REG_SIZE_U64 as u64
| u64::from(KVM_REG_ARM_CORE)
| ((offset__of!(user_pt_regs, $reg) / mem::size_of::<u32>()) as u64)
};
}
// This macro computes the ID of a specific ARM64 system register similar to how
// the kernel C macro does.
// https://elixir.bootlin.com/linux/v4.20.17/source/arch/arm64/include/uapi/asm/kvm.h#L203
macro_rules! arm64_sys_reg {
($name: tt, $op0: tt, $op1: tt, $crn: tt, $crm: tt, $op2: tt) => {
const $name: u64 = KVM_REG_ARM64 as u64
| KVM_REG_SIZE_U64 as u64
| KVM_REG_ARM64_SYSREG as u64
| ((($op0 as u64) << KVM_REG_ARM64_SYSREG_OP0_SHIFT)
& KVM_REG_ARM64_SYSREG_OP0_MASK as u64)
| ((($op1 as u64) << KVM_REG_ARM64_SYSREG_OP1_SHIFT)
& KVM_REG_ARM64_SYSREG_OP1_MASK as u64)
| ((($crn as u64) << KVM_REG_ARM64_SYSREG_CRN_SHIFT)
& KVM_REG_ARM64_SYSREG_CRN_MASK as u64)
| ((($crm as u64) << KVM_REG_ARM64_SYSREG_CRM_SHIFT)
& KVM_REG_ARM64_SYSREG_CRM_MASK as u64)
| ((($op2 as u64) << KVM_REG_ARM64_SYSREG_OP2_SHIFT)
& KVM_REG_ARM64_SYSREG_OP2_MASK as u64);
};
}
// Constant imported from the Linux kernel:
// https://elixir.bootlin.com/linux/v4.20.17/source/arch/arm64/include/asm/sysreg.h#L135
arm64_sys_reg!(MPIDR_EL1, 3, 0, 0, 0, 5);
/// Configure core registers for a given CPU.
///
/// # Arguments
///
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
/// * `cpu_id` - Index of current vcpu.
/// * `boot_ip` - Starting instruction pointer.
/// * `mem` - Reserved DRAM for current VM.
pub fn setup_regs(vcpu: &VcpuFd, cpu_id: u8, boot_ip: u64, mem: &GuestMemoryMmap) -> Result<()> {
// Get the register index of the PSTATE (Processor State) register.
vcpu.set_one_reg(arm64_core_reg!(pstate), PSTATE_FAULT_BITS_64)
.map_err(Error::SetCoreRegister)?;
// Other vCPUs are powered off initially awaiting PSCI wakeup.
if cpu_id == 0 {
// Setting the PC (Processor Counter) to the current program address (kernel address).
vcpu.set_one_reg(arm64_core_reg!(pc), boot_ip)
.map_err(Error::SetCoreRegister)?;
// Last mandatory thing to set -> the address pointing to the FDT (also called DTB).
// "The device tree blob (dtb) must be placed on an 8-byte boundary and must
// not exceed 2 megabytes in size." -> https://www.kernel.org/doc/Documentation/arm64/booting.txt.
// We are choosing to place it the end of DRAM. See `get_fdt_addr`.
vcpu.set_one_reg(arm64_core_reg!(regs), get_fdt_addr(mem) as u64)
.map_err(Error::SetCoreRegister)?;
}
Ok(())
}
/// Read the MPIDR - Multiprocessor Affinity Register.
///
/// # Arguments
///
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
pub fn read_mpidr(vcpu: &VcpuFd) -> Result<u64> {
vcpu.get_one_reg(MPIDR_EL1).map_err(Error::GetSysRegister)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::aarch64::layout;
use kvm_ioctls::Kvm;
use vm_memory::{GuestAddress, GuestMemoryMmap};
#[test]
fn test_setup_regs() {
let kvm = Kvm::new().unwrap();
let vm = kvm.create_vm().unwrap();
let vcpu = vm.create_vcpu(0).unwrap();
let mut regions = Vec::new();
regions.push((
GuestAddress(layout::RAM_64BIT_START),
(layout::FDT_MAX_SIZE + 0x1000) as usize,
));
let mem = GuestMemoryMmap::from_ranges(&regions).expect("Cannot initialize memory");
match setup_regs(&vcpu, 0, 0x0, &mem).unwrap_err() {
Error::SetCoreRegister(ref e) => assert_eq!(e.errno(), libc::ENOEXEC),
_ => panic!("Expected to receive Error::SetCoreRegister"),
}
let mut kvi: kvm_bindings::kvm_vcpu_init = kvm_bindings::kvm_vcpu_init::default();
vm.get_preferred_target(&mut kvi).unwrap();
vcpu.vcpu_init(&kvi).unwrap();
assert!(setup_regs(&vcpu, 0, 0x0, &mem).is_ok());
}
#[test]
fn test_read_mpidr() {
let kvm = Kvm::new().unwrap();
let vm = kvm.create_vm().unwrap();
let vcpu = vm.create_vcpu(0).unwrap();
let mut kvi: kvm_bindings::kvm_vcpu_init = kvm_bindings::kvm_vcpu_init::default();
vm.get_preferred_target(&mut kvi).unwrap();
// Must fail when vcpu is not initialized yet.
assert!(read_mpidr(&vcpu).is_err());
vcpu.vcpu_init(&kvi).unwrap();
assert_eq!(read_mpidr(&vcpu).unwrap(), 0x80000000);
}
}

View File

@@ -1,8 +1,11 @@
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
// Copyright © 2020, Oracle and/or its affiliates. // Copyright © 2020, Oracle and/or its affiliates.
// //
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
//! Implements platform specific functionality.
//! Supported platforms: x86_64, aarch64.
#![allow( #![allow(
clippy::unreadable_literal, clippy::unreadable_literal,
clippy::redundant_static_lifetimes, clippy::redundant_static_lifetimes,
@@ -13,22 +16,29 @@
extern crate byteorder; extern crate byteorder;
extern crate kvm_bindings; extern crate kvm_bindings;
extern crate kvm_ioctls;
extern crate libc; extern crate libc;
extern crate vm_memory;
#[cfg(feature = "acpi")] #[cfg(feature = "acpi")]
extern crate acpi_tables; extern crate acpi_tables;
extern crate arch_gen; extern crate arch_gen;
extern crate kvm_ioctls;
extern crate linux_loader; extern crate linux_loader;
extern crate vm_memory;
use kvm_ioctls::*;
use std::fmt;
use std::result; use std::result;
/// Type for returning error code.
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
/// X86_64 specific error triggered during system configuration. /// X86_64 specific error triggered during system configuration.
X86_64Setup(x86_64::Error), X86_64Setup(x86_64::Error),
#[cfg(target_arch = "aarch64")]
/// AArch64 specific error triggered during system configuration.
AArch64Setup(aarch64::Error),
/// The zero page extends past the end of guest_mem. /// The zero page extends past the end of guest_mem.
ZeroPagePastRamEnd, ZeroPagePastRamEnd,
/// Error writing the zero page of guest memory. /// Error writing the zero page of guest memory.
@@ -41,10 +51,21 @@ pub enum Error {
StartInfoPastRamEnd, StartInfoPastRamEnd,
/// Error writing hvm_start_info to guest memory. /// Error writing hvm_start_info to guest memory.
StartInfoSetup, StartInfoSetup,
/// Failed to compute initramfs address.
InitramfsAddress,
/// Error writing module entry to guest memory.
ModlistSetup(vm_memory::GuestMemoryError),
/// RSDP Beyond Guest Memory
RSDPPastRamEnd,
/// Capability missing
CapabilityMissing(Cap),
} }
/// Type for returning public functions outcome.
pub type Result<T> = result::Result<T, Error>; pub type Result<T> = result::Result<T, Error>;
#[derive(PartialEq)] /// Type for memory region types.
#[derive(PartialEq, Debug)]
pub enum RegionType { pub enum RegionType {
/// RAM type /// RAM type
Ram, Ram,
@@ -62,13 +83,15 @@ pub enum RegionType {
Reserved, Reserved,
} }
/// Module for aarch64 related functionality.
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
pub mod aarch64; pub mod aarch64;
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
pub use aarch64::{ pub use aarch64::{
arch_memory_regions, configure_system, get_reserved_mem_addr, layout::CMDLINE_MAX_SIZE, arch_memory_regions, check_required_kvm_extensions, configure_system, configure_vcpu,
layout::CMDLINE_START, fdt::DeviceInfoForFDT, get_host_cpu_phys_bits, get_kernel_start, layout,
layout::CMDLINE_MAX_SIZE, layout::IRQ_BASE, layout::IRQ_MAX, EntryPoint,
}; };
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
@@ -76,6 +99,66 @@ pub mod x86_64;
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
pub use x86_64::{ pub use x86_64::{
arch_memory_regions, configure_system, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START, arch_memory_regions, check_required_kvm_extensions, configure_system, configure_vcpu,
BootProtocol, EntryPoint, get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
layout::CMDLINE_START, regs, BootProtocol, CpuidPatch, CpuidReg, EntryPoint,
}; };
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
#[cfg(target_arch = "x86_64")]
#[inline(always)]
fn pagesize() -> usize {
// Trivially safe
unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
}
/// Type for passing information about the initramfs in the guest memory.
pub struct InitramfsConfig {
/// Load address of initramfs in guest memory
pub address: vm_memory::GuestAddress,
/// Size of initramfs in guest memory
pub size: usize,
}
/// Types of devices that can get attached to this platform.
#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
pub enum DeviceType {
/// Device Type: Virtio.
Virtio(u32),
/// Device Type: Serial.
#[cfg(target_arch = "aarch64")]
Serial,
/// Device Type: RTC.
#[cfg(target_arch = "aarch64")]
RTC,
}
/// Default (smallest) memory page size for the supported architectures.
pub const PAGE_SIZE: usize = 4096;
impl fmt::Display for DeviceType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
/// Structure to describe MMIO device information
#[derive(Clone, Debug)]
#[cfg(target_arch = "aarch64")]
pub struct MMIODeviceInfo {
pub addr: u64,
pub irq: u32,
}
#[cfg(target_arch = "aarch64")]
impl DeviceInfoForFDT for MMIODeviceInfo {
fn addr(&self) -> u64 {
self.addr
}
fn irq(&self) -> u32 {
self.irq
}
fn length(&self) -> u64 {
4096 as u64
}
}

View File

@@ -31,6 +31,10 @@ pub const BOOT_IDT_START: GuestAddress = GuestAddress(0x520);
/// Address for the hvm_start_info struct used in PVH boot /// Address for the hvm_start_info struct used in PVH boot
pub const PVH_INFO_START: GuestAddress = GuestAddress(0x6000); pub const PVH_INFO_START: GuestAddress = GuestAddress(0x6000);
/// Starting address of array of modules of hvm_modlist_entry type.
/// Used to enable initrd support using the PVH boot ABI.
pub const MODLIST_START: GuestAddress = GuestAddress(0x6040);
/// Address of memory map table used in PVH boot. Can overlap /// Address of memory map table used in PVH boot. Can overlap
/// with the zero page address since they are mutually exclusive. /// with the zero page address since they are mutually exclusive.
pub const MEMMAP_START: GuestAddress = GuestAddress(0x7000); pub const MEMMAP_START: GuestAddress = GuestAddress(0x7000);
@@ -43,9 +47,10 @@ pub const BOOT_STACK_START: GuestAddress = GuestAddress(0x8000);
pub const BOOT_STACK_POINTER: GuestAddress = GuestAddress(0x8ff0); pub const BOOT_STACK_POINTER: GuestAddress = GuestAddress(0x8ff0);
// Initial pagetables. // Initial pagetables.
pub const PML4_START: GuestAddress = GuestAddress(0x9000); pub const PML5_START: GuestAddress = GuestAddress(0x9000);
pub const PDPTE_START: GuestAddress = GuestAddress(0xa000); pub const PML4_START: GuestAddress = GuestAddress(0xa000);
pub const PDE_START: GuestAddress = GuestAddress(0xb000); pub const PDPTE_START: GuestAddress = GuestAddress(0xb000);
pub const PDE_START: GuestAddress = GuestAddress(0xc000);
/// Kernel command line start address. /// Kernel command line start address.
pub const CMDLINE_START: GuestAddress = GuestAddress(0x20000); pub const CMDLINE_START: GuestAddress = GuestAddress(0x20000);
@@ -74,18 +79,18 @@ pub const HIGH_RAM_START: GuestAddress = GuestAddress(0x100000);
// ** 32-bit reserved area (start: 3GiB, length: 1GiB) ** // ** 32-bit reserved area (start: 3GiB, length: 1GiB) **
pub const MEM_32BIT_RESERVED_START: GuestAddress = GuestAddress(0xc000_0000); pub const MEM_32BIT_RESERVED_START: GuestAddress = GuestAddress(0xc000_0000);
pub const MEM_32BIT_RESERVED_SIZE: GuestUsize = (1024 << 20); pub const MEM_32BIT_RESERVED_SIZE: GuestUsize = 1024 << 20;
// == Fixed constants within the "32-bit reserved" range == // == Fixed constants within the "32-bit reserved" range ==
// Sub range: 32-bit PCI devices (start: 3GiB, length: 640Mib) // Sub range: 32-bit PCI devices (start: 3GiB, length: 640Mib)
pub const MEM_32BIT_DEVICES_START: GuestAddress = MEM_32BIT_RESERVED_START; pub const MEM_32BIT_DEVICES_START: GuestAddress = MEM_32BIT_RESERVED_START;
pub const MEM_32BIT_DEVICES_SIZE: GuestUsize = (640 << 20); pub const MEM_32BIT_DEVICES_SIZE: GuestUsize = 640 << 20;
// PCI MMCONFIG space (start: after the device space, length: 256MiB) // PCI MMCONFIG space (start: after the device space, length: 256MiB)
pub const PCI_MMCONFIG_START: GuestAddress = pub const PCI_MMCONFIG_START: GuestAddress =
GuestAddress(MEM_32BIT_DEVICES_START.0 + MEM_32BIT_DEVICES_SIZE); GuestAddress(MEM_32BIT_DEVICES_START.0 + MEM_32BIT_DEVICES_SIZE);
pub const PCI_MMCONFIG_SIZE: GuestUsize = (256 << 20); pub const PCI_MMCONFIG_SIZE: GuestUsize = 256 << 20;
// IOAPIC // IOAPIC
pub const IOAPIC_START: GuestAddress = GuestAddress(0xfec0_0000); pub const IOAPIC_START: GuestAddress = GuestAddress(0xfec0_0000);

View File

@@ -10,15 +10,21 @@
mod gdt; mod gdt;
pub mod interrupts; pub mod interrupts;
pub mod layout; pub mod layout;
#[cfg(not(feature = "acpi"))]
mod mptable; mod mptable;
pub mod regs; pub mod regs;
use crate::InitramfsConfig;
use crate::RegionType; use crate::RegionType;
use kvm_bindings::CpuId;
use kvm_ioctls::*;
use linux_loader::loader::bootparam::{boot_params, setup_header}; use linux_loader::loader::bootparam::{boot_params, setup_header};
use linux_loader::loader::start_info::{hvm_memmap_table_entry, hvm_start_info}; use linux_loader::loader::elf::start_info::{
hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info,
};
use std::mem; use std::mem;
use vm_memory::{ use vm_memory::{
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap, GuestUsize, Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
GuestMemoryMmap, GuestMemoryRegion, GuestUsize,
}; };
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
@@ -45,6 +51,8 @@ pub struct EntryPoint {
pub entry_addr: GuestAddress, pub entry_addr: GuestAddress,
/// Specifies which boot protocol to use /// Specifies which boot protocol to use
pub protocol: BootProtocol, pub protocol: BootProtocol,
/// This field is used for bzImage to fill zero page
pub setup_header: Option<setup_header>,
} }
const E820_RAM: u32 = 1; const E820_RAM: u32 = 1;
@@ -66,6 +74,11 @@ struct MemmapTableEntryWrapper(hvm_memmap_table_entry);
unsafe impl ByteValued for MemmapTableEntryWrapper {} unsafe impl ByteValued for MemmapTableEntryWrapper {}
#[derive(Copy, Clone, Default)]
struct ModlistEntryWrapper(hvm_modlist_entry);
unsafe impl ByteValued for ModlistEntryWrapper {}
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign // This is a workaround to the Rust enforcement specifying that any implementation of a foreign
// trait (in this case `DataInit`) where: // trait (in this case `DataInit`) where:
// * the type that is implementing the trait is foreign or // * the type that is implementing the trait is foreign or
@@ -81,8 +94,27 @@ unsafe impl ByteValued for BootParamsWrapper {}
pub enum Error { pub enum Error {
/// Invalid e820 setup params. /// Invalid e820 setup params.
E820Configuration, E820Configuration,
#[cfg(not(feature = "acpi"))]
/// Error writing MP table to memory. /// Error writing MP table to memory.
MpTableSetup(mptable::Error), MpTableSetup(mptable::Error),
/// Error configuring the general purpose registers
REGSConfiguration(regs::Error),
/// Error configuring the special registers
SREGSConfiguration(regs::Error),
/// Error configuring the floating point related registers
FPUConfiguration(regs::Error),
/// Error configuring the MSR registers
MSRSConfiguration(regs::Error),
/// The call to KVM_SET_CPUID2 failed.
SetSupportedCpusFailed(kvm_ioctls::Error),
/// Cannot set the local interruption due to bad configuration.
LocalIntConfiguration(interrupts::Error),
} }
impl From<Error> for super::Error { impl From<Error> for super::Error {
@@ -91,6 +123,113 @@ impl From<Error> for super::Error {
} }
} }
#[allow(dead_code)]
#[derive(Copy, Clone)]
pub enum CpuidReg {
EAX,
EBX,
ECX,
EDX,
}
pub struct CpuidPatch {
pub function: u32,
pub index: u32,
pub flags_bit: Option<u8>,
pub eax_bit: Option<u8>,
pub ebx_bit: Option<u8>,
pub ecx_bit: Option<u8>,
pub edx_bit: Option<u8>,
}
impl CpuidPatch {
pub fn set_cpuid_reg(
cpuid: &mut CpuId,
function: u32,
index: Option<u32>,
reg: CpuidReg,
value: u32,
) {
let entries = cpuid.as_mut_slice();
for entry in entries.iter_mut() {
if entry.function == function && (index == None || index.unwrap() == entry.index) {
match reg {
CpuidReg::EAX => {
entry.eax = value;
}
CpuidReg::EBX => {
entry.ebx = value;
}
CpuidReg::ECX => {
entry.ecx = value;
}
CpuidReg::EDX => {
entry.edx = value;
}
}
}
}
}
pub fn patch_cpuid(cpuid: &mut CpuId, patches: Vec<CpuidPatch>) {
let entries = cpuid.as_mut_slice();
for entry in entries.iter_mut() {
for patch in patches.iter() {
if entry.function == patch.function && entry.index == patch.index {
if let Some(flags_bit) = patch.flags_bit {
entry.flags |= 1 << flags_bit;
}
if let Some(eax_bit) = patch.eax_bit {
entry.eax |= 1 << eax_bit;
}
if let Some(ebx_bit) = patch.ebx_bit {
entry.ebx |= 1 << ebx_bit;
}
if let Some(ecx_bit) = patch.ecx_bit {
entry.ecx |= 1 << ecx_bit;
}
if let Some(edx_bit) = patch.edx_bit {
entry.edx |= 1 << edx_bit;
}
}
}
}
}
}
pub fn configure_vcpu(
fd: &VcpuFd,
id: u8,
kernel_entry_point: Option<EntryPoint>,
vm_memory: &GuestMemoryAtomic<GuestMemoryMmap>,
cpuid: CpuId,
) -> super::Result<()> {
let mut cpuid = cpuid;
CpuidPatch::set_cpuid_reg(&mut cpuid, 0xb, None, CpuidReg::EDX, u32::from(id));
fd.set_cpuid2(&cpuid)
.map_err(Error::SetSupportedCpusFailed)?;
regs::setup_msrs(fd).map_err(Error::MSRSConfiguration)?;
if let Some(kernel_entry_point) = kernel_entry_point {
// Safe to unwrap because this method is called after the VM is configured
regs::setup_regs(
fd,
kernel_entry_point.entry_addr.raw_value(),
layout::BOOT_STACK_POINTER.raw_value(),
layout::ZERO_PAGE_START.raw_value(),
kernel_entry_point.protocol,
)
.map_err(Error::REGSConfiguration)?;
regs::setup_fpu(fd).map_err(Error::FPUConfiguration)?;
regs::setup_sregs(&vm_memory.memory(), fd, kernel_entry_point.protocol)
.map_err(Error::SREGSConfiguration)?;
}
interrupts::set_lint(fd).map_err(Error::LocalIntConfiguration)?;
Ok(())
}
/// Returns a Vec of the valid memory addresses. /// Returns a Vec of the valid memory addresses.
/// These should be used to configure the GuestMemory structure for the platform. /// These should be used to configure the GuestMemory structure for the platform.
/// For x86_64 all addresses are valid from the start of the kernel except a /// For x86_64 all addresses are valid from the start of the kernel except a
@@ -151,20 +290,36 @@ pub fn configure_system(
guest_mem: &GuestMemoryMmap, guest_mem: &GuestMemoryMmap,
cmdline_addr: GuestAddress, cmdline_addr: GuestAddress,
cmdline_size: usize, cmdline_size: usize,
num_cpus: u8, initramfs: &Option<InitramfsConfig>,
_num_cpus: u8,
setup_hdr: Option<setup_header>, setup_hdr: Option<setup_header>,
rsdp_addr: Option<GuestAddress>, rsdp_addr: Option<GuestAddress>,
boot_prot: BootProtocol, boot_prot: BootProtocol,
) -> super::Result<()> { ) -> super::Result<()> {
// Note that this puts the mptable at the last 1k of Linux's 640k base RAM // Note that this puts the mptable at the last 1k of Linux's 640k base RAM
mptable::setup_mptable(guest_mem, num_cpus).map_err(Error::MpTableSetup)?; #[cfg(not(feature = "acpi"))]
mptable::setup_mptable(guest_mem, _num_cpus).map_err(Error::MpTableSetup)?;
// Check that the RAM is not smaller than the RSDP start address
if let Some(rsdp_addr) = rsdp_addr {
if rsdp_addr.0 > guest_mem.last_addr().0 {
return Err(super::Error::RSDPPastRamEnd);
}
}
match boot_prot { match boot_prot {
BootProtocol::PvhBoot => { BootProtocol::PvhBoot => {
configure_pvh(guest_mem, cmdline_addr, rsdp_addr)?; configure_pvh(guest_mem, cmdline_addr, initramfs, rsdp_addr)?;
} }
BootProtocol::LinuxBoot => { BootProtocol::LinuxBoot => {
configure_64bit_boot(guest_mem, cmdline_addr, cmdline_size, setup_hdr, rsdp_addr)?; configure_64bit_boot(
guest_mem,
cmdline_addr,
cmdline_size,
initramfs,
setup_hdr,
rsdp_addr,
)?;
} }
} }
@@ -174,6 +329,7 @@ pub fn configure_system(
fn configure_pvh( fn configure_pvh(
guest_mem: &GuestMemoryMmap, guest_mem: &GuestMemoryMmap,
cmdline_addr: GuestAddress, cmdline_addr: GuestAddress,
initramfs: &Option<InitramfsConfig>,
rsdp_addr: Option<GuestAddress>, rsdp_addr: Option<GuestAddress>,
) -> super::Result<()> { ) -> super::Result<()> {
const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336ec578; const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336ec578;
@@ -190,6 +346,24 @@ fn configure_pvh(
start_info.0.rsdp_paddr = rsdp_addr.0; start_info.0.rsdp_paddr = rsdp_addr.0;
} }
if let Some(initramfs_config) = initramfs {
// The initramfs has been written to guest memory already, here we just need to
// create the module structure that describes it.
let ramdisk_mod: ModlistEntryWrapper = ModlistEntryWrapper(hvm_modlist_entry {
paddr: initramfs_config.address.raw_value(),
size: initramfs_config.size as u64,
..Default::default()
});
start_info.0.nr_modules += 1;
start_info.0.modlist_paddr = layout::MODLIST_START.raw_value();
// Write the modlist struct to guest memory.
guest_mem
.write_obj(ramdisk_mod, layout::MODLIST_START)
.map_err(super::Error::ModlistSetup)?;
}
// Vector to hold the memory maps which needs to be written to guest memory // Vector to hold the memory maps which needs to be written to guest memory
// at MEMMAP_START after all of the mappings are recorded. // at MEMMAP_START after all of the mappings are recorded.
let mut memmap: Vec<hvm_memmap_table_entry> = Vec::new(); let mut memmap: Vec<hvm_memmap_table_entry> = Vec::new();
@@ -293,6 +467,7 @@ fn configure_64bit_boot(
guest_mem: &GuestMemoryMmap, guest_mem: &GuestMemoryMmap,
cmdline_addr: GuestAddress, cmdline_addr: GuestAddress,
cmdline_size: usize, cmdline_size: usize,
initramfs: &Option<InitramfsConfig>,
setup_hdr: Option<setup_header>, setup_hdr: Option<setup_header>,
rsdp_addr: Option<GuestAddress>, rsdp_addr: Option<GuestAddress>,
) -> super::Result<()> { ) -> super::Result<()> {
@@ -319,6 +494,11 @@ fn configure_64bit_boot(
params.0.hdr.cmd_line_ptr = cmdline_addr.raw_value() as u32; params.0.hdr.cmd_line_ptr = cmdline_addr.raw_value() as u32;
params.0.hdr.cmdline_size = cmdline_size as u32; params.0.hdr.cmdline_size = cmdline_size as u32;
if let Some(initramfs_config) = initramfs {
params.0.hdr.ramdisk_image = initramfs_config.address.raw_value() as u32;
params.0.hdr.ramdisk_size = initramfs_config.size as u32;
}
add_e820_entry(&mut params.0, 0, layout::EBDA_START.raw_value(), E820_RAM)?; add_e820_entry(&mut params.0, 0, layout::EBDA_START.raw_value(), E820_RAM)?;
let mem_end = guest_mem.last_addr(); let mem_end = guest_mem.last_addr();
@@ -388,6 +568,66 @@ fn add_e820_entry(
Ok(()) Ok(())
} }
/// Returns the memory address where the initramfs could be loaded.
pub fn initramfs_load_addr(
guest_mem: &GuestMemoryMmap,
initramfs_size: usize,
) -> super::Result<u64> {
let first_region = guest_mem
.find_region(GuestAddress::new(0))
.ok_or(super::Error::InitramfsAddress)?;
// It's safe to cast to usize because the size of a region can't be greater than usize.
let lowmem_size = first_region.len() as usize;
if lowmem_size < initramfs_size {
return Err(super::Error::InitramfsAddress);
}
let aligned_addr: u64 = ((lowmem_size - initramfs_size) & !(crate::pagesize() - 1)) as u64;
Ok(aligned_addr)
}
pub fn get_host_cpu_phys_bits() -> u8 {
use std::arch::x86_64;
unsafe {
let leaf = x86_64::__cpuid(0x8000_0000);
// Detect and handle AMD SME (Secure Memory Encryption) properly.
// Some physical address bits may become reserved when the feature is enabled.
// See AMD64 Architecture Programmer's Manual Volume 2, Section 7.10.1
let reduced = if leaf.eax >= 0x8000_001f
&& leaf.ebx == 0x6874_7541 // Vendor ID: AuthenticAMD
&& leaf.ecx == 0x444d_4163
&& leaf.edx == 0x6974_6e65
&& x86_64::__cpuid(0x8000_001f).eax & 0x1 != 0
{
(x86_64::__cpuid(0x8000_001f).ebx >> 6) & 0x3f
} else {
0
};
if leaf.eax >= 0x8000_0008 {
let leaf = x86_64::__cpuid(0x8000_0008);
((leaf.eax & 0xff) - reduced) as u8
} else {
36
}
}
}
pub fn check_required_kvm_extensions(kvm: &Kvm) -> super::Result<()> {
if !kvm.check_extension(Cap::SignalMsi) {
return Err(super::Error::CapabilityMissing(Cap::SignalMsi));
}
if !kvm.check_extension(Cap::TscDeadlineTimer) {
return Err(super::Error::CapabilityMissing(Cap::TscDeadlineTimer));
}
if !kvm.check_extension(Cap::SplitIrqchip) {
return Err(super::Error::CapabilityMissing(Cap::SplitIrqchip));
}
Ok(())
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -417,9 +657,10 @@ mod tests {
&gm, &gm,
GuestAddress(0), GuestAddress(0),
0, 0,
&None,
1, 1,
None, None,
None, Some(layout::RSDP_POINTER),
BootProtocol::LinuxBoot, BootProtocol::LinuxBoot,
); );
assert!(config_err.is_err()); assert!(config_err.is_err());
@@ -437,6 +678,7 @@ mod tests {
&gm, &gm,
GuestAddress(0), GuestAddress(0),
0, 0,
&None,
no_vcpus, no_vcpus,
None, None,
None, None,
@@ -448,6 +690,7 @@ mod tests {
&gm, &gm,
GuestAddress(0), GuestAddress(0),
0, 0,
&None,
no_vcpus, no_vcpus,
None, None,
None, None,
@@ -468,6 +711,7 @@ mod tests {
&gm, &gm,
GuestAddress(0), GuestAddress(0),
0, 0,
&None,
no_vcpus, no_vcpus,
None, None,
None, None,
@@ -479,6 +723,7 @@ mod tests {
&gm, &gm,
GuestAddress(0), GuestAddress(0),
0, 0,
&None,
no_vcpus, no_vcpus,
None, None,
None, None,
@@ -499,6 +744,7 @@ mod tests {
&gm, &gm,
GuestAddress(0), GuestAddress(0),
0, 0,
&None,
no_vcpus, no_vcpus,
None, None,
None, None,
@@ -510,6 +756,7 @@ mod tests {
&gm, &gm,
GuestAddress(0), GuestAddress(0),
0, 0,
&None,
no_vcpus, no_vcpus,
None, None,
None, None,

View File

@@ -14,7 +14,9 @@ use super::BootProtocol;
use arch_gen::x86::msr_index; use arch_gen::x86::msr_index;
use kvm_bindings::{kvm_fpu, kvm_msr_entry, kvm_regs, kvm_sregs, Msrs}; use kvm_bindings::{kvm_fpu, kvm_msr_entry, kvm_regs, kvm_sregs, Msrs};
use kvm_ioctls::VcpuFd; use kvm_ioctls::VcpuFd;
use layout::{BOOT_GDT_START, BOOT_IDT_START, PDE_START, PDPTE_START, PML4_START, PVH_INFO_START}; use layout::{
BOOT_GDT_START, BOOT_IDT_START, PDE_START, PDPTE_START, PML4_START, PML5_START, PVH_INFO_START,
};
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryMmap}; use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryMmap};
// MTRR constants // MTRR constants
@@ -45,6 +47,8 @@ pub enum Error {
WritePDEAddress(GuestMemoryError), WritePDEAddress(GuestMemoryError),
/// Writing PML4 to RAM failed. /// Writing PML4 to RAM failed.
WritePML4Address(GuestMemoryError), WritePML4Address(GuestMemoryError),
/// Writing PML5 to RAM failed.
WritePML5Address(GuestMemoryError),
} }
pub type Result<T> = result::Result<T, Error>; pub type Result<T> = result::Result<T, Error>;
@@ -70,7 +74,7 @@ pub fn setup_fpu(vcpu: &VcpuFd) -> Result<()> {
/// ///
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd. /// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
pub fn setup_msrs(vcpu: &VcpuFd) -> Result<()> { pub fn setup_msrs(vcpu: &VcpuFd) -> Result<()> {
vcpu.set_msrs(&create_msr_entries()) vcpu.set_msrs(&boot_msr_entries())
.map_err(Error::SetModelSpecificRegisters)?; .map_err(Error::SetModelSpecificRegisters)?;
Ok(()) Ok(())
@@ -138,6 +142,7 @@ const EFER_LME: u64 = 0x100;
const X86_CR0_PE: u64 = 0x1; const X86_CR0_PE: u64 = 0x1;
const X86_CR0_PG: u64 = 0x80000000; const X86_CR0_PG: u64 = 0x80000000;
const X86_CR4_PAE: u64 = 0x20; const X86_CR4_PAE: u64 = 0x20;
const X86_CR4_LA57: u64 = 0x1000;
fn write_gdt_table(table: &[u64], guest_mem: &GuestMemoryMmap) -> Result<()> { fn write_gdt_table(table: &[u64], guest_mem: &GuestMemoryMmap) -> Result<()> {
let boot_gdt_addr = BOOT_GDT_START; let boot_gdt_addr = BOOT_GDT_START;
@@ -220,7 +225,18 @@ fn configure_segments_and_sregs(
} }
fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()> { fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()> {
// Puts PML4 right after zero page but aligned to 4k. // Puts PML5 or PML4 right after zero page but aligned to 4k.
if unsafe { std::arch::x86_64::__cpuid(7).ecx } & (1 << 16) != 0 {
// Entry covering VA [0..256TB)
mem.write_obj(PML4_START.raw_value() | 0x03, PML5_START)
.map_err(Error::WritePML5Address)?;
sregs.cr3 = PML5_START.raw_value();
sregs.cr4 |= X86_CR4_LA57;
} else {
sregs.cr3 = PML4_START.raw_value();
}
// Entry covering VA [0..512GB) // Entry covering VA [0..512GB)
mem.write_obj(PDPTE_START.raw_value() | 0x03, PML4_START) mem.write_obj(PDPTE_START.raw_value() | 0x03, PML4_START)
@@ -229,6 +245,7 @@ fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()>
// Entry covering VA [0..1GB) // Entry covering VA [0..1GB)
mem.write_obj(PDE_START.raw_value() | 0x03, PDPTE_START) mem.write_obj(PDE_START.raw_value() | 0x03, PDPTE_START)
.map_err(Error::WritePDPTEAddress)?; .map_err(Error::WritePDPTEAddress)?;
// 512 2MB entries together covering VA [0..1GB). Note we are assuming // 512 2MB entries together covering VA [0..1GB). Note we are assuming
// CPU supports 2MB pages (/proc/cpuinfo has 'pse'). All modern CPUs do. // CPU supports 2MB pages (/proc/cpuinfo has 'pse'). All modern CPUs do.
for i in 0..512 { for i in 0..512 {
@@ -236,74 +253,49 @@ fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()>
.map_err(Error::WritePDEAddress)?; .map_err(Error::WritePDEAddress)?;
} }
sregs.cr3 = PML4_START.raw_value();
sregs.cr4 |= X86_CR4_PAE; sregs.cr4 |= X86_CR4_PAE;
sregs.cr0 |= X86_CR0_PG; sregs.cr0 |= X86_CR0_PG;
Ok(()) Ok(())
} }
fn create_msr_entries() -> Msrs { macro_rules! kvm_msr {
let mut entries = Vec::<kvm_msr_entry>::new(); ($msr:expr) => {
kvm_msr_entry {
index: $msr,
data: 0x0,
..Default::default()
}
};
}
entries.push(kvm_msr_entry { macro_rules! kvm_msr_data {
index: msr_index::MSR_IA32_SYSENTER_CS, ($msr:expr, $data:expr) => {
data: 0x0, kvm_msr_entry {
..Default::default() index: $msr,
}); data: $data,
entries.push(kvm_msr_entry { ..Default::default()
index: msr_index::MSR_IA32_SYSENTER_ESP, }
data: 0x0, };
..Default::default() }
});
entries.push(kvm_msr_entry {
index: msr_index::MSR_IA32_SYSENTER_EIP,
data: 0x0,
..Default::default()
});
// x86_64 specific msrs, we only run on x86_64 not x86.
entries.push(kvm_msr_entry {
index: msr_index::MSR_STAR,
data: 0x0,
..Default::default()
});
entries.push(kvm_msr_entry {
index: msr_index::MSR_CSTAR,
data: 0x0,
..Default::default()
});
entries.push(kvm_msr_entry {
index: msr_index::MSR_KERNEL_GS_BASE,
data: 0x0,
..Default::default()
});
entries.push(kvm_msr_entry {
index: msr_index::MSR_SYSCALL_MASK,
data: 0x0,
..Default::default()
});
entries.push(kvm_msr_entry {
index: msr_index::MSR_LSTAR,
data: 0x0,
..Default::default()
});
// end of x86_64 specific code
entries.push(kvm_msr_entry {
index: msr_index::MSR_IA32_TSC,
data: 0x0,
..Default::default()
});
entries.push(kvm_msr_entry {
index: msr_index::MSR_IA32_MISC_ENABLE,
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()
});
Msrs::from_entries(&entries) pub fn boot_msr_entries() -> Msrs {
Msrs::from_entries(&[
kvm_msr!(msr_index::MSR_IA32_SYSENTER_CS),
kvm_msr!(msr_index::MSR_IA32_SYSENTER_ESP),
kvm_msr!(msr_index::MSR_IA32_SYSENTER_EIP),
kvm_msr!(msr_index::MSR_STAR),
kvm_msr!(msr_index::MSR_CSTAR),
kvm_msr!(msr_index::MSR_LSTAR),
kvm_msr!(msr_index::MSR_KERNEL_GS_BASE),
kvm_msr!(msr_index::MSR_SYSCALL_MASK),
kvm_msr!(msr_index::MSR_IA32_TSC),
kvm_msr_data!(
msr_index::MSR_IA32_MISC_ENABLE,
msr_index::MSR_IA32_MISC_ENABLE_FAST_STRING as u64
),
kvm_msr_data!(msr_index::MSR_MTRRdefType, MTRR_ENABLE | MTRR_MEM_TYPE_WB),
])
} }
#[cfg(test)] #[cfg(test)]
@@ -393,8 +385,11 @@ mod tests {
let gm = create_guest_mem(); let gm = create_guest_mem();
setup_page_tables(&gm, &mut sregs).unwrap(); setup_page_tables(&gm, &mut sregs).unwrap();
assert_eq!(0xa003, read_u64(&gm, PML4_START)); if unsafe { std::arch::x86_64::__cpuid(7).ecx } & (1 << 16) != 0 {
assert_eq!(0xb003, read_u64(&gm, PDPTE_START)); assert_eq!(0xa003, read_u64(&gm, PML5_START));
}
assert_eq!(0xb003, read_u64(&gm, PML4_START));
assert_eq!(0xc003, read_u64(&gm, PDPTE_START));
for i in 0..512 { for i in 0..512 {
assert_eq!( assert_eq!(
(i << 21) + 0x83u64, (i << 21) + 0x83u64,
@@ -402,7 +397,11 @@ mod tests {
); );
} }
assert_eq!(PML4_START.raw_value(), sregs.cr3); if unsafe { std::arch::x86_64::__cpuid(7).ecx } & (1 << 16) != 0 {
assert_eq!(PML5_START.raw_value(), sregs.cr3);
} else {
assert_eq!(PML4_START.raw_value(), sregs.cr3);
}
assert_eq!(X86_CR4_PAE, sregs.cr4); assert_eq!(X86_CR4_PAE, sregs.cr4);
assert_eq!(X86_CR0_PG, sregs.cr0); assert_eq!(X86_CR0_PG, sregs.cr0);
} }
@@ -451,7 +450,7 @@ mod tests {
// Official entries that were setup when we did setup_msrs. We need to assert that the // Official entries that were setup when we did setup_msrs. We need to assert that the
// tenth one (i.e the one with index msr_index::MSR_IA32_MISC_ENABLE has the data we // tenth one (i.e the one with index msr_index::MSR_IA32_MISC_ENABLE has the data we
// expect. // expect.
let entry_vec = create_msr_entries(); let entry_vec = boot_msr_entries();
assert_eq!(entry_vec.as_slice()[9], msrs.as_slice()[0]); assert_eq!(entry_vec.as_slice()[9], msrs.as_slice()[0]);
} }

View File

@@ -4,14 +4,19 @@ version = "0.1.0"
authors = ["The Chromium OS Authors"] authors = ["The Chromium OS Authors"]
[dependencies] [dependencies]
anyhow = "1.0"
bitflags = ">=1.2.1" bitflags = ">=1.2.1"
byteorder = "1.3.4" byteorder = "1.3.4"
epoll = ">=4.0.1" epoll = ">=4.0.1"
libc = "0.2.68" libc = "0.2.71"
log = "0.4.8" log = "0.4.8"
serde = {version = ">=1.0.27", features = ["rc"] }
serde_derive = ">=1.0.27"
serde_json = ">=1.0.9"
vm-device = { path = "../vm-device" } vm-device = { path = "../vm-device" }
acpi_tables = { path = "../acpi_tables", optional = true } acpi_tables = { path = "../acpi_tables", optional = true }
vm-memory = "0.1.0" vm-memory = "0.2.1"
vm-migration = { path = "../vm-migration" }
vmm-sys-util = ">=0.3.1" vmm-sys-util = ">=0.3.1"
[dev-dependencies] [dev-dependencies]
@@ -21,3 +26,4 @@ tempfile = "3.1.0"
default = [] default = []
acpi = ["acpi_tables"] acpi = ["acpi_tables"]
cmos = [] cmos = []
fwdebug = []

85
devices/src/gic.rs Normal file
View File

@@ -0,0 +1,85 @@
// Copyright 2020, ARM Limited.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use super::interrupt_controller::{Error, InterruptController};
use std::result;
use std::sync::Arc;
use vm_device::interrupt::{
InterruptIndex, InterruptManager, InterruptSourceGroup, MsiIrqGroupConfig,
};
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
type Result<T> = result::Result<T, Error>;
// Reserve 32 IRQs (GSI 32 ~ 64) for legacy device.
// GsiAllocator should allocate beyond this: from 64 on
pub const IRQ_LEGACY_COUNT: usize = 32;
pub const IRQ_SPI_OFFSET: usize = 32;
// This Gic struct implements InterruptController to provide interrupt delivery service.
// The Gic source files in arch/ folder maintain the Aarch64 specific Gic device.
// The 2 Gic instances could be merged together.
// Leave this refactoring to future. Two options may be considered:
// 1. Move Gic*.rs from arch/ folder here.
// 2. Move this file and ioapic.rs to arch/, as they are architecture specific.
pub struct Gic {
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
}
impl Gic {
pub fn new(
_vcpu_count: u8,
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
) -> Result<Gic> {
let interrupt_source_group = interrupt_manager
.create_group(MsiIrqGroupConfig {
base: IRQ_SPI_OFFSET as InterruptIndex,
count: IRQ_LEGACY_COUNT as InterruptIndex,
})
.map_err(Error::CreateInterruptSourceGroup)?;
Ok(Gic {
interrupt_source_group,
})
}
}
impl InterruptController for Gic {
fn enable(&self) -> Result<()> {
&self
.interrupt_source_group
.enable()
.map_err(Error::EnableInterrupt)?;
Ok(())
}
// This should be called anytime an interrupt needs to be injected into the
// running guest.
fn service_irq(&mut self, irq: usize) -> Result<()> {
self.interrupt_source_group
.trigger(irq as InterruptIndex)
.map_err(Error::TriggerInterrupt)?;
Ok(())
}
}
const GIC_SNAPSHOT_ID: &str = "gic";
impl Snapshottable for Gic {
fn id(&self) -> String {
GIC_SNAPSHOT_ID.to_string()
}
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
unimplemented!();
}
fn restore(&mut self, _snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
unimplemented!();
}
}
impl Pausable for Gic {}
impl Transportable for Gic {}
impl Migratable for Gic {}

View File

@@ -0,0 +1,61 @@
// Copyright 2020, ARM Limited.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::io;
use std::result;
#[derive(Debug)]
pub enum Error {
/// Invalid destination mode.
InvalidDestinationMode,
/// Invalid trigger mode.
InvalidTriggerMode,
/// Invalid delivery mode.
InvalidDeliveryMode,
/// Failed creating the interrupt source group.
CreateInterruptSourceGroup(io::Error),
/// Failed triggering the interrupt.
TriggerInterrupt(io::Error),
/// Failed masking the interrupt.
MaskInterrupt(io::Error),
/// Failed unmasking the interrupt.
UnmaskInterrupt(io::Error),
/// Failed updating the interrupt.
UpdateInterrupt(io::Error),
/// Failed enabling the interrupt.
EnableInterrupt(io::Error),
}
type Result<T> = result::Result<T, Error>;
pub struct MsiMessage {
// Message Address Register
// 31-20: Base address. Fixed value (0x0FEE)
// 19-12: Destination ID
// 11-4: Reserved
// 3: Redirection Hint indication
// 2: Destination Mode
// 1-0: Reserved
pub addr: u32,
// Message Data Register
// 32-16: Reserved
// 15: Trigger Mode. 0 = Edge, 1 = Level
// 14: Level. 0 = Deassert, 1 = Assert
// 13-11: Reserved
// 10-8: Delivery Mode
// 7-0: Vector
pub data: u32,
}
// Introduce trait InterruptController to uniform the interrupt
// service provided for devices.
// Device manager uses this trait without caring whether it is a
// IOAPIC (X86) or GIC (Arm).
pub trait InterruptController: Send {
fn service_irq(&mut self, irq: usize) -> Result<()>;
#[cfg(target_arch = "aarch64")]
fn enable(&self) -> Result<()>;
#[cfg(target_arch = "x86_64")]
fn end_of_interrupt(&mut self, vec: u8);
}

View File

@@ -9,9 +9,10 @@
// Implementation of an intel 82093AA Input/Output Advanced Programmable Interrupt Controller // Implementation of an intel 82093AA Input/Output Advanced Programmable Interrupt Controller
// See https://pdos.csail.mit.edu/6.828/2016/readings/ia32/ioapic.pdf for a specification. // See https://pdos.csail.mit.edu/6.828/2016/readings/ia32/ioapic.pdf for a specification.
use super::interrupt_controller::{Error, InterruptController};
use crate::BusDevice; use crate::BusDevice;
use anyhow::anyhow;
use byteorder::{ByteOrder, LittleEndian}; use byteorder::{ByteOrder, LittleEndian};
use std::io;
use std::result; use std::result;
use std::sync::Arc; use std::sync::Arc;
use vm_device::interrupt::{ use vm_device::interrupt::{
@@ -19,28 +20,14 @@ use vm_device::interrupt::{
MsiIrqGroupConfig, MsiIrqSourceConfig, MsiIrqGroupConfig, MsiIrqSourceConfig,
}; };
use vm_memory::GuestAddress; use vm_memory::GuestAddress;
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
Transportable,
};
#[derive(Debug)] #[derive(Serialize, Deserialize)]
pub enum Error { #[serde(remote = "GuestAddress")]
/// Invalid destination mode. pub struct GuestAddressDef(pub u64);
InvalidDestinationMode,
/// Invalid trigger mode.
InvalidTriggerMode,
/// Invalid delivery mode.
InvalidDeliveryMode,
/// Failed creating the interrupt source group.
CreateInterruptSourceGroup(io::Error),
/// Failed triggering the interrupt.
TriggerInterrupt(io::Error),
/// Failed masking the interrupt.
MaskInterrupt(io::Error),
/// Failed unmasking the interrupt.
UnmaskInterrupt(io::Error),
/// Failed updating the interrupt.
UpdateInterrupt(io::Error),
/// Failed enabling the interrupt.
EnableInterrupt(io::Error),
}
type Result<T> = result::Result<T, Error>; type Result<T> = result::Result<T, Error>;
@@ -99,25 +86,6 @@ fn set_remote_irr(entry: &mut RedirectionTableEntry, val: u8) {
*entry |= u64::from(val & 0x1) << 14; *entry |= u64::from(val & 0x1) << 14;
} }
pub struct MsiMessage {
// Message Address Register
// 31-20: Base address. Fixed value (0x0FEE)
// 19-12: Destination ID
// 11-4: Reserved
// 3: Redirection Hint indication
// 2: Destination Mode
// 1-0: Reserved
pub addr: u32,
// Message Data Register
// 32-16: Reserved
// 15: Trigger Mode. 0 = Edge, 1 = Level
// 14: Level. 0 = Deassert, 1 = Assert
// 13-11: Reserved
// 10-8: Delivery Mode
// 7-0: Vector
pub data: u32,
}
pub const NUM_IOAPIC_PINS: usize = 24; pub const NUM_IOAPIC_PINS: usize = 24;
const IOAPIC_VERSION_ID: u32 = 0x0017_0011; const IOAPIC_VERSION_ID: u32 = 0x0017_0011;
@@ -166,13 +134,25 @@ fn decode_irq_from_selector(selector: u8) -> (usize, bool) {
} }
pub struct Ioapic { pub struct Ioapic {
id: u32, id: String,
id_reg: u32,
reg_sel: u32, reg_sel: u32,
reg_entries: [RedirectionTableEntry; NUM_IOAPIC_PINS], reg_entries: [RedirectionTableEntry; NUM_IOAPIC_PINS],
used_entries: [bool; NUM_IOAPIC_PINS],
apic_address: GuestAddress, apic_address: GuestAddress,
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>, interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
} }
#[derive(Serialize, Deserialize)]
pub struct IoapicState {
id_reg: u32,
reg_sel: u32,
reg_entries: [RedirectionTableEntry; NUM_IOAPIC_PINS],
used_entries: [bool; NUM_IOAPIC_PINS],
#[serde(with = "GuestAddressDef")]
apic_address: GuestAddress,
}
impl BusDevice for Ioapic { impl BusDevice for Ioapic {
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) { fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
assert!(data.len() == 4); assert!(data.len() == 4);
@@ -210,6 +190,7 @@ impl BusDevice for Ioapic {
impl Ioapic { impl Ioapic {
pub fn new( pub fn new(
id: String,
apic_address: GuestAddress, apic_address: GuestAddress,
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>, interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
) -> Result<Ioapic> { ) -> Result<Ioapic> {
@@ -225,43 +206,87 @@ impl Ioapic {
.map_err(Error::EnableInterrupt)?; .map_err(Error::EnableInterrupt)?;
Ok(Ioapic { Ok(Ioapic {
id: 0, id,
id_reg: 0,
reg_sel: 0, reg_sel: 0,
reg_entries: [0; NUM_IOAPIC_PINS], reg_entries: [0; NUM_IOAPIC_PINS],
used_entries: [false; NUM_IOAPIC_PINS],
apic_address, apic_address,
interrupt_source_group, interrupt_source_group,
}) })
} }
// The ioapic must be informed about EOIs in order to deassert interrupts fn ioapic_write(&mut self, val: u32) {
// already sent. debug!("IOAPIC_W reg 0x{:x}, val 0x{:x}", self.reg_sel, val);
pub fn end_of_interrupt(&mut self, vec: u8) {
for i in 0..NUM_IOAPIC_PINS { match self.reg_sel as u8 {
let entry = &mut self.reg_entries[i]; IOAPIC_REG_ID => self.id_reg = (val >> 24) & 0xf,
// Clear Remote IRR bit IOWIN_OFF..=REG_MAX_OFFSET => {
if vector(*entry) == vec && trigger_mode(*entry) == 1 { let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
set_remote_irr(entry, 0); if is_high_bits {
self.reg_entries[index] &= 0xffff_ffff;
self.reg_entries[index] |= u64::from(val) << 32;
} else {
// Ensure not to override read-only bits:
// - Delivery Status (bit 12)
// - Remote IRR (bit 14)
self.reg_entries[index] &= 0xffff_ffff_0000_5000;
self.reg_entries[index] |= u64::from(val) & 0xffff_afff;
}
// The entry must be updated through the interrupt source
// group.
if let Err(e) = self.update_entry(index) {
error!("Failed updating IOAPIC entry: {:?}", e);
}
// Store the information this IRQ is now being used.
self.used_entries[index] = true;
}
_ => error!("IOAPIC: invalid write to register offset"),
}
}
fn ioapic_read(&self) -> u32 {
debug!("IOAPIC_R reg 0x{:x}", self.reg_sel);
match self.reg_sel as u8 {
IOAPIC_REG_VERSION => IOAPIC_VERSION_ID,
IOAPIC_REG_ID | IOAPIC_REG_ARBITRATION_ID => (self.id_reg & 0xf) << 24,
IOWIN_OFF..=REG_MAX_OFFSET => {
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
if is_high_bits {
(self.reg_entries[index] >> 32) as u32
} else {
(self.reg_entries[index] & 0xffff_ffff) as u32
}
}
_ => {
error!("IOAPIC: invalid read from register offset");
0
} }
} }
} }
// This should be called anytime an interrupt needs to be injected into the fn state(&self) -> IoapicState {
// running guest. IoapicState {
pub fn service_irq(&mut self, irq: usize) -> Result<()> { id_reg: self.id_reg,
let entry = &mut self.reg_entries[irq]; reg_sel: self.reg_sel,
reg_entries: self.reg_entries,
self.interrupt_source_group used_entries: self.used_entries,
.trigger(irq as InterruptIndex) apic_address: self.apic_address,
.map_err(Error::TriggerInterrupt)?; }
debug!("Interrupt successfully delivered"); }
// If trigger mode is level sensitive, set the Remote IRR bit. fn set_state(&mut self, state: &IoapicState) -> Result<()> {
// It will be cleared when the EOI is received. self.id_reg = state.id_reg;
if trigger_mode(*entry) == 1 { self.reg_sel = state.reg_sel;
set_remote_irr(entry, 1); self.reg_entries = state.reg_entries;
self.used_entries = state.used_entries;
self.apic_address = state.apic_address;
for (irq, entry) in self.used_entries.iter().enumerate() {
if *entry {
self.update_entry(irq)?;
}
} }
// Clear the Delivery Status bit
set_delivery_status(entry, 0);
Ok(()) Ok(())
} }
@@ -337,52 +362,84 @@ impl Ioapic {
Ok(()) Ok(())
} }
}
fn ioapic_write(&mut self, val: u32) { impl InterruptController for Ioapic {
debug!("IOAPIC_W reg 0x{:x}, val 0x{:x}", self.reg_sel, val); // The ioapic must be informed about EOIs in order to deassert interrupts
// already sent.
match self.reg_sel as u8 { fn end_of_interrupt(&mut self, vec: u8) {
IOAPIC_REG_ID => self.id = (val >> 24) & 0xf, for i in 0..NUM_IOAPIC_PINS {
IOWIN_OFF..=REG_MAX_OFFSET => { let entry = &mut self.reg_entries[i];
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8); // Clear Remote IRR bit
if is_high_bits { if vector(*entry) == vec && trigger_mode(*entry) == 1 {
self.reg_entries[index] &= 0xffff_ffff; set_remote_irr(entry, 0);
self.reg_entries[index] |= u64::from(val) << 32;
} else {
// Ensure not to override read-only bits:
// - Delivery Status (bit 12)
// - Remote IRR (bit 14)
self.reg_entries[index] &= 0xffff_ffff_0000_5000;
self.reg_entries[index] |= u64::from(val) & 0xffff_afff;
}
// The entry must be updated through the interrupt source
// group.
if let Err(e) = self.update_entry(index) {
error!("Failed updating IOAPIC entry: {:?}", e);
}
} }
_ => error!("IOAPIC: invalid write to register offset"),
} }
} }
fn ioapic_read(&self) -> u32 { // This should be called anytime an interrupt needs to be injected into the
debug!("IOAPIC_R reg 0x{:x}", self.reg_sel); // running guest.
fn service_irq(&mut self, irq: usize) -> Result<()> {
let entry = &mut self.reg_entries[irq];
match self.reg_sel as u8 { self.interrupt_source_group
IOAPIC_REG_VERSION => IOAPIC_VERSION_ID, .trigger(irq as InterruptIndex)
IOAPIC_REG_ID | IOAPIC_REG_ARBITRATION_ID => (self.id & 0xf) << 24, .map_err(Error::TriggerInterrupt)?;
IOWIN_OFF..=REG_MAX_OFFSET => { debug!("Interrupt successfully delivered");
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
if is_high_bits { // If trigger mode is level sensitive, set the Remote IRR bit.
(self.reg_entries[index] >> 32) as u32 // It will be cleared when the EOI is received.
} else { if trigger_mode(*entry) == 1 {
(self.reg_entries[index] & 0xffff_ffff) as u32 set_remote_irr(entry, 1);
}
}
_ => {
error!("IOAPIC: invalid read from register offset");
0
}
} }
// Clear the Delivery Status bit
set_delivery_status(entry, 0);
Ok(())
} }
} }
impl Snapshottable for Ioapic {
fn id(&self) -> String {
self.id.clone()
}
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
let snapshot =
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
let mut ioapic_snapshot = Snapshot::new(self.id.as_str());
ioapic_snapshot.add_data_section(SnapshotDataSection {
id: format!("{}-section", self.id),
snapshot,
});
Ok(ioapic_snapshot)
}
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
if let Some(ioapic_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
let ioapic_state = match serde_json::from_slice(&ioapic_section.snapshot) {
Ok(state) => state,
Err(error) => {
return Err(MigratableError::Restore(anyhow!(
"Could not deserialize IOAPIC {}",
error
)))
}
};
return self.set_state(&ioapic_state).map_err(|e| {
MigratableError::Restore(anyhow!("Could not restore IOAPIC state {:?}", e))
});
}
Err(MigratableError::Restore(anyhow!(
"Could not find IOAPIC snapshot section"
)))
}
}
impl Pausable for Ioapic {}
impl Transportable for Ioapic {}
impl Migratable for Ioapic {}

View File

@@ -0,0 +1,40 @@
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
//
// Copyright © 2020 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
//
use BusDevice;
/// Provides firmware debug output via I/O port controls
#[derive(Default)]
pub struct FwDebugDevice {}
impl FwDebugDevice {
pub fn new() -> Self {
Self {}
}
}
/// FwDebugDevice sits on the I/O bus as 0x402 and receives ASCII characters
impl BusDevice for FwDebugDevice {
/// Upon read return the magic value to indicate that there is a debug port
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
if data.len() == 1 {
data[0] = 0xe9
} else {
error!("Invalid read size on debug port: {}", data.len())
}
}
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) {
if data.len() == 1 {
print!("{}", data[0] as char);
} else {
error!("Invalid write size on debug port: {}", data.len())
}
}
}

View File

@@ -7,10 +7,19 @@
#[cfg(feature = "cmos")] #[cfg(feature = "cmos")]
mod cmos; mod cmos;
#[cfg(feature = "fwdebug")]
mod fwdebug;
mod i8042; mod i8042;
#[cfg(target_arch = "aarch64")]
mod rtc_pl031;
mod serial; mod serial;
#[cfg(feature = "cmos")] #[cfg(feature = "cmos")]
pub use self::cmos::Cmos; pub use self::cmos::Cmos;
#[cfg(feature = "fwdebug")]
pub use self::fwdebug::FwDebugDevice;
pub use self::i8042::I8042Device; pub use self::i8042::I8042Device;
pub use self::serial::Serial; pub use self::serial::Serial;
#[cfg(target_arch = "aarch64")]
pub use self::rtc_pl031::RTC;

View File

@@ -0,0 +1,626 @@
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//! ARM PL031 Real Time Clock
//!
//! This module implements a PL031 Real Time Clock (RTC) that provides to provides long time base counter.
//! This is achieved by generating an interrupt signal after counting for a programmed number of cycles of
//! a real-time clock input.
//!
use std::fmt;
use std::sync::Arc;
use std::time::Instant;
use std::{io, result};
use crate::BusDevice;
use vm_device::interrupt::InterruptSourceGroup;
// As you can see in https://static.docs.arm.com/ddi0224/c/real_time_clock_pl031_r1p3_technical_reference_manual_DDI0224C.pdf
// at section 3.2 Summary of RTC registers, the total size occupied by this device is 0x000 -> 0xFFC + 4 = 0x1000.
// From 0x0 to 0x1C we have following registers:
const RTCDR: u64 = 0x0; // Data Register.
const RTCMR: u64 = 0x4; // Match Register.
const RTCLR: u64 = 0x8; // Load Regiser.
const RTCCR: u64 = 0xc; // Control Register.
const RTCIMSC: u64 = 0x10; // Interrupt Mask Set or Clear Register.
const RTCRIS: u64 = 0x14; // Raw Interrupt Status.
const RTCMIS: u64 = 0x18; // Masked Interrupt Status.
const RTCICR: u64 = 0x1c; // Interrupt Clear Register.
// From 0x020 to 0xFDC => reserved space.
// From 0xFE0 to 0x1000 => Peripheral and PrimeCell Identification Registers which are Read Only registers.
// AMBA standard devices have CIDs (Cell IDs) and PIDs (Peripheral IDs). The linux kernel will look for these in order to assert the identity
// of these devices (i.e look at the `amba_device_try_add` function).
// We are putting the expected values (look at 'Reset value' column from above mentioned document) in an array.
const PL031_ID: [u8; 8] = [0x31, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
// We are only interested in the margins.
const AMBA_ID_LOW: u64 = 0xFE0;
const AMBA_ID_HIGH: u64 = 0x1000;
/// Constant to convert seconds to nanoseconds.
pub const NANOS_PER_SECOND: u64 = 1_000_000_000;
#[allow(unused_macros)]
macro_rules! generate_read_fn {
($fn_name: ident, $data_type: ty, $byte_type: ty, $type_size: expr, $endian_type: ident) => {
#[allow(dead_code)]
pub fn $fn_name(input: &[$byte_type]) -> $data_type {
assert!($type_size == std::mem::size_of::<$data_type>());
let mut array = [0u8; $type_size];
for (byte, read) in array.iter_mut().zip(input.iter().cloned()) {
*byte = read as u8;
}
<$data_type>::$endian_type(array)
}
};
}
#[allow(unused_macros)]
macro_rules! generate_write_fn {
($fn_name: ident, $data_type: ty, $byte_type: ty, $endian_type: ident) => {
#[allow(dead_code)]
pub fn $fn_name(buf: &mut [$byte_type], n: $data_type) {
for (byte, read) in buf
.iter_mut()
.zip(<$data_type>::$endian_type(n).iter().cloned())
{
*byte = read as $byte_type;
}
}
};
}
generate_read_fn!(read_le_u16, u16, u8, 2, from_le_bytes);
generate_read_fn!(read_le_u32, u32, u8, 4, from_le_bytes);
generate_read_fn!(read_le_u64, u64, u8, 8, from_le_bytes);
generate_read_fn!(read_le_i32, i32, i8, 4, from_le_bytes);
generate_read_fn!(read_be_u16, u16, u8, 2, from_be_bytes);
generate_read_fn!(read_be_u32, u32, u8, 4, from_be_bytes);
generate_write_fn!(write_le_u16, u16, u8, to_le_bytes);
generate_write_fn!(write_le_u32, u32, u8, to_le_bytes);
generate_write_fn!(write_le_u64, u64, u8, to_le_bytes);
generate_write_fn!(write_le_i32, i32, i8, to_le_bytes);
generate_write_fn!(write_be_u16, u16, u8, to_be_bytes);
generate_write_fn!(write_be_u32, u32, u8, to_be_bytes);
#[derive(Debug)]
pub enum Error {
BadWriteOffset(u64),
InterruptFailure(io::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::BadWriteOffset(offset) => write!(f, "Bad Write Offset: {}", offset),
Error::InterruptFailure(e) => write!(f, "Failed to trigger interrupt: {}", e),
}
}
}
type Result<T> = result::Result<T, Error>;
/// Wrapper over `libc::clockid_t` to specify Linux Kernel clock source.
pub enum ClockType {
/// Equivalent to `libc::CLOCK_MONOTONIC`.
Monotonic,
/// Equivalent to `libc::CLOCK_REALTIME`.
#[allow(dead_code)]
Real,
/// Equivalent to `libc::CLOCK_PROCESS_CPUTIME_ID`.
ProcessCpu,
/// Equivalent to `libc::CLOCK_THREAD_CPUTIME_ID`.
#[allow(dead_code)]
ThreadCpu,
}
impl Into<libc::clockid_t> for ClockType {
fn into(self) -> libc::clockid_t {
match self {
ClockType::Monotonic => libc::CLOCK_MONOTONIC,
ClockType::Real => libc::CLOCK_REALTIME,
ClockType::ProcessCpu => libc::CLOCK_PROCESS_CPUTIME_ID,
ClockType::ThreadCpu => libc::CLOCK_THREAD_CPUTIME_ID,
}
}
}
/// Structure representing the date in local time with nanosecond precision.
pub struct LocalTime {
/// Seconds in current minute.
sec: i32,
/// Minutes in current hour.
min: i32,
/// Hours in current day, 24H format.
hour: i32,
/// Days in current month.
mday: i32,
/// Months in current year.
mon: i32,
/// Years passed since 1900 BC.
year: i32,
/// Nanoseconds in current second.
nsec: i64,
}
impl LocalTime {
/// Returns the [LocalTime](struct.LocalTime.html) structure for the calling moment.
#[allow(dead_code)]
pub fn now() -> LocalTime {
let mut timespec = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
let mut tm: libc::tm = libc::tm {
tm_sec: 0,
tm_min: 0,
tm_hour: 0,
tm_mday: 0,
tm_mon: 0,
tm_year: 0,
tm_wday: 0,
tm_yday: 0,
tm_isdst: 0,
tm_gmtoff: 0,
tm_zone: std::ptr::null(),
};
// Safe because the parameters are valid.
unsafe {
libc::clock_gettime(libc::CLOCK_REALTIME, &mut timespec);
libc::localtime_r(&timespec.tv_sec, &mut tm);
}
LocalTime {
sec: tm.tm_sec,
min: tm.tm_min,
hour: tm.tm_hour,
mday: tm.tm_mday,
mon: tm.tm_mon,
year: tm.tm_year,
nsec: timespec.tv_nsec,
}
}
}
impl fmt::Display for LocalTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
self.year + 1900,
self.mon + 1,
self.mday,
self.hour,
self.min,
self.sec,
self.nsec
)
}
}
/// Holds a micro-second resolution timestamp with both the real time and cpu time.
#[derive(Clone)]
pub struct TimestampUs {
/// Real time in microseconds.
pub time_us: u64,
/// Cpu time in microseconds.
pub cputime_us: u64,
}
impl Default for TimestampUs {
fn default() -> TimestampUs {
TimestampUs {
time_us: get_time(ClockType::Monotonic) / 1000,
cputime_us: get_time(ClockType::ProcessCpu) / 1000,
}
}
}
/// Returns a timestamp in nanoseconds from a monotonic clock.
///
/// Uses `_rdstc` on `x86_64` and [`get_time`](fn.get_time.html) on other architectures.
#[allow(dead_code)]
pub fn timestamp_cycles() -> u64 {
#[cfg(target_arch = "x86_64")]
// Safe because there's nothing that can go wrong with this call.
unsafe {
std::arch::x86_64::_rdtsc() as u64
}
#[cfg(not(target_arch = "x86_64"))]
{
get_time(ClockType::Monotonic)
}
}
/// Returns a timestamp in nanoseconds based on the provided clock type.
///
/// # Arguments
///
/// * `clock_type` - Identifier of the Linux Kernel clock on which to act.
pub fn get_time(clock_type: ClockType) -> u64 {
let mut time_struct = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
// Safe because the parameters are valid.
unsafe { libc::clock_gettime(clock_type.into(), &mut time_struct) };
seconds_to_nanoseconds(time_struct.tv_sec).unwrap() as u64 + (time_struct.tv_nsec as u64)
}
/// Converts a timestamp in seconds to an equivalent one in nanoseconds.
/// Returns `None` if the conversion overflows.
///
/// # Arguments
///
/// * `value` - Timestamp in seconds.
pub fn seconds_to_nanoseconds(value: i64) -> Option<i64> {
value.checked_mul(NANOS_PER_SECOND as i64)
}
/// A RTC device following the PL031 specification..
pub struct RTC {
previous_now: Instant,
tick_offset: i64,
// This is used for implementing the RTC alarm. However, in Firecracker we do not need it.
match_value: u32,
// Writes to this register load an update value into the RTC.
load: u32,
imsc: u32,
ris: u32,
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
}
impl RTC {
/// Constructs an AMBA PL031 RTC device.
pub fn new(interrupt: Arc<Box<dyn InterruptSourceGroup>>) -> RTC {
RTC {
// This is used only for duration measuring purposes.
previous_now: Instant::now(),
tick_offset: get_time(ClockType::Real) as i64,
match_value: 0,
load: 0,
imsc: 0,
ris: 0,
interrupt,
}
}
fn trigger_interrupt(&mut self) -> Result<()> {
self.interrupt.trigger(0).map_err(Error::InterruptFailure)?;
Ok(())
}
fn get_time(&self) -> u32 {
let ts = (self.tick_offset as i128)
+ (Instant::now().duration_since(self.previous_now).as_nanos() as i128);
(ts / NANOS_PER_SECOND as i128) as u32
}
fn handle_write(&mut self, offset: u64, val: u32) -> Result<()> {
match offset {
RTCMR => {
// The MR register is used for implementing the RTC alarm. A real time clock alarm is
// a feature that can be used to allow a computer to 'wake up' after shut down to execute
// tasks every day or on a certain day. It can sometimes be found in the 'Power Management'
// section of a motherboard's BIOS setup. This is functionality that extends beyond
// Firecracker intended use. However, we increment a metric just in case.
self.match_value = val;
}
RTCLR => {
self.load = val;
self.previous_now = Instant::now();
// If the unwrap fails, then the internal value of the clock has been corrupted and
// we want to terminate the execution of the process.
self.tick_offset = seconds_to_nanoseconds(i64::from(val)).unwrap();
}
RTCIMSC => {
self.imsc = val & 1;
self.trigger_interrupt()?;
}
RTCICR => {
// As per above mentioned doc, the interrupt is cleared by writing any data value to
// the Interrupt Clear Register.
self.ris = 0;
self.trigger_interrupt()?;
}
RTCCR => (), // ignore attempts to turn off the timer.
o => {
return Err(Error::BadWriteOffset(o));
}
}
Ok(())
}
}
impl BusDevice for RTC {
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
let v;
let mut read_ok = true;
if offset < AMBA_ID_HIGH && offset >= AMBA_ID_LOW {
let index = ((offset - AMBA_ID_LOW) >> 2) as usize;
v = u32::from(PL031_ID[index]);
} else {
v = match offset {
RTCDR => self.get_time(),
RTCMR => {
// Even though we are not implementing RTC alarm we return the last value
self.match_value
}
RTCLR => self.load,
RTCCR => 1, // RTC is always enabled.
RTCIMSC => self.imsc,
RTCRIS => self.ris,
RTCMIS => self.ris & self.imsc,
_ => {
read_ok = false;
0
}
};
}
if read_ok && data.len() <= 4 {
write_le_u32(data, v);
} else {
warn!(
"Invalid RTC PL031 read: offset {}, data length {}",
offset,
data.len()
);
}
}
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
if data.len() <= 4 {
let v = read_le_u32(&data[..]);
if let Err(e) = self.handle_write(offset, v) {
warn!("Failed to write to RTC PL031 device: {}", e);
}
} else {
warn!(
"Invalid RTC PL031 write: offset {}, data length {}",
offset,
data.len()
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
use vmm_sys_util::eventfd::EventFd;
const LEGACY_RTC_MAPPED_IO_START: u64 = 0x0901_0000;
#[test]
fn test_get_time() {
for _ in 0..1000 {
assert!(get_time(ClockType::Monotonic) <= get_time(ClockType::Monotonic));
}
for _ in 0..1000 {
assert!(get_time(ClockType::ProcessCpu) <= get_time(ClockType::ProcessCpu));
}
for _ in 0..1000 {
assert!(get_time(ClockType::ThreadCpu) <= get_time(ClockType::ThreadCpu));
}
assert_ne!(get_time(ClockType::Real), 0);
}
#[test]
fn test_local_time_display() {
let local_time = LocalTime {
sec: 30,
min: 15,
hour: 10,
mday: 4,
mon: 6,
year: 119,
nsec: 123_456_789,
};
assert_eq!(
String::from("2019-07-04T10:15:30.123456789"),
local_time.to_string()
);
let local_time = LocalTime {
sec: 5,
min: 5,
hour: 5,
mday: 23,
mon: 7,
year: 44,
nsec: 123,
};
assert_eq!(
String::from("1944-08-23T05:05:05.000000123"),
local_time.to_string()
);
let local_time = LocalTime::now();
assert!(local_time.mon >= 0 && local_time.mon <= 11);
}
#[test]
fn test_seconds_to_nanoseconds() {
assert_eq!(
seconds_to_nanoseconds(100).unwrap() as u64,
100 * NANOS_PER_SECOND
);
assert!(seconds_to_nanoseconds(9_223_372_037).is_none());
}
struct TestInterrupt {
event_fd: EventFd,
}
impl InterruptSourceGroup for TestInterrupt {
fn trigger(&self, _index: InterruptIndex) -> result::Result<(), std::io::Error> {
self.event_fd.write(1)
}
fn update(
&self,
_index: InterruptIndex,
_config: InterruptSourceConfig,
) -> result::Result<(), std::io::Error> {
Ok(())
}
fn notifier(&self, _index: InterruptIndex) -> Option<&EventFd> {
Some(&self.event_fd)
}
}
impl TestInterrupt {
fn new(event_fd: EventFd) -> Self {
TestInterrupt { event_fd }
}
}
#[test]
fn test_rtc_read_write_and_event() {
let intr_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap();
let mut rtc = RTC::new(Arc::new(Box::new(TestInterrupt::new(
intr_evt.try_clone().unwrap(),
))));
let mut data = [0; 4];
// Read and write to the MR register.
write_le_u32(&mut data, 123);
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCMR, &mut data);
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCMR, &mut data);
let v = read_le_u32(&data[..]);
assert_eq!(v, 123);
// Read and write to the LR register.
let v = get_time(ClockType::Real);
write_le_u32(&mut data, (v / NANOS_PER_SECOND) as u32);
let previous_now_before = rtc.previous_now;
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCLR, &mut data);
assert!(rtc.previous_now > previous_now_before);
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCLR, &mut data);
let v_read = read_le_u32(&data[..]);
assert_eq!((v / NANOS_PER_SECOND) as u32, v_read);
// Read and write to IMSC register.
// Test with non zero value.
let non_zero = 1;
write_le_u32(&mut data, non_zero);
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &mut data);
// The interrupt line should be on.
assert!(rtc.interrupt.notifier(0).unwrap().read().unwrap() == 1);
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &mut data);
let v = read_le_u32(&data[..]);
assert_eq!(non_zero & 1, v);
// Now test with 0.
write_le_u32(&mut data, 0);
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &mut data);
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &mut data);
let v = read_le_u32(&data[..]);
assert_eq!(0, v);
// Read and write to the ICR register.
write_le_u32(&mut data, 1);
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCICR, &mut data);
// The interrupt line should be on.
assert!(rtc.interrupt.notifier(0).unwrap().read().unwrap() > 1);
let v_before = read_le_u32(&data[..]);
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCICR, &mut data);
let v = read_le_u32(&data[..]);
// ICR is a write only register. Data received should stay equal to data sent.
assert_eq!(v, v_before);
// Attempts to turn off the RTC should not go through.
write_le_u32(&mut data, 0);
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCCR, &mut data);
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCCR, &mut data);
let v = read_le_u32(&data[..]);
assert_eq!(v, 1);
// Attempts to write beyond the writable space. Using here the space used to read
// the CID and PID from.
write_le_u32(&mut data, 0);
rtc.write(LEGACY_RTC_MAPPED_IO_START, AMBA_ID_LOW, &mut data);
// However, reading from the AMBA_ID_LOW should succeed upon read.
let mut data = [0; 4];
rtc.read(LEGACY_RTC_MAPPED_IO_START, AMBA_ID_LOW, &mut data);
let index = AMBA_ID_LOW + 3;
assert_eq!(data[0], PL031_ID[((index - AMBA_ID_LOW) >> 2) as usize]);
}
macro_rules! byte_order_test_read_write {
($test_name: ident, $write_fn_name: ident, $read_fn_name: ident, $is_be: expr, $data_type: ty) => {
#[test]
fn $test_name() {
#[allow(overflowing_literals)]
let test_cases = [
(
0x0123_4567_89AB_CDEF as u64,
[0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef],
),
(
0x0000_0000_0000_0000 as u64,
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
),
(
0x1923_2345_ABF3_CCD4 as u64,
[0x19, 0x23, 0x23, 0x45, 0xAB, 0xF3, 0xCC, 0xD4],
),
(
0x0FF0_0FF0_0FF0_0FF0 as u64,
[0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0],
),
(
0xFFFF_FFFF_FFFF_FFFF as u64,
[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF],
),
(
0x89AB_12D4_C2D2_09BB as u64,
[0x89, 0xAB, 0x12, 0xD4, 0xC2, 0xD2, 0x09, 0xBB],
),
];
let type_size = std::mem::size_of::<$data_type>();
for (test_val, v_arr) in &test_cases {
let v = *test_val as $data_type;
let cmp_iter: Box<dyn Iterator<Item = _>> = if $is_be {
Box::new(v_arr[(8 - type_size)..].iter())
} else {
Box::new(v_arr.iter().rev())
};
// test write
let mut write_arr = vec![Default::default(); type_size];
$write_fn_name(&mut write_arr, v);
for (cmp, cur) in cmp_iter.zip(write_arr.iter()) {
assert_eq!(*cmp, *cur as u8)
}
// test read
let read_val = $read_fn_name(&write_arr);
assert_eq!(v, read_val);
}
}
};
}
byte_order_test_read_write!(test_le_u16, write_le_u16, read_le_u16, false, u16);
byte_order_test_read_write!(test_le_u32, write_le_u32, read_le_u32, false, u32);
byte_order_test_read_write!(test_le_u64, write_le_u64, read_le_u64, false, u64);
byte_order_test_read_write!(test_le_i32, write_le_i32, read_le_i32, false, i32);
byte_order_test_read_write!(test_be_u16, write_be_u16, read_be_u16, true, u16);
byte_order_test_read_write!(test_be_u32, write_be_u32, read_be_u32, true, u32);
}

View File

@@ -6,10 +6,15 @@
// found in the LICENSE-BSD-3-Clause file. // found in the LICENSE-BSD-3-Clause file.
use crate::BusDevice; use crate::BusDevice;
use anyhow::anyhow;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::sync::Arc; use std::sync::Arc;
use std::{io, result}; use std::{io, result};
use vm_device::interrupt::InterruptSourceGroup; use vm_device::interrupt::InterruptSourceGroup;
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
Transportable,
};
use vmm_sys_util::errno::Result; use vmm_sys_util::errno::Result;
const LOOP_SIZE: usize = 0x40; const LOOP_SIZE: usize = 0x40;
@@ -55,6 +60,7 @@ const DEFAULT_BAUD_DIVISOR: u16 = 12; // 9600 bps
/// This can optionally write the guest's output to a Write trait object. To send input to the /// This can optionally write the guest's output to a Write trait object. To send input to the
/// guest, use `queue_input_bytes`. /// guest, use `queue_input_bytes`.
pub struct Serial { pub struct Serial {
id: String,
interrupt_enable: u8, interrupt_enable: u8,
interrupt_identification: u8, interrupt_identification: u8,
interrupt: Arc<Box<dyn InterruptSourceGroup>>, interrupt: Arc<Box<dyn InterruptSourceGroup>>,
@@ -68,12 +74,27 @@ pub struct Serial {
out: Option<Box<dyn io::Write + Send>>, out: Option<Box<dyn io::Write + Send>>,
} }
#[derive(Serialize, Deserialize)]
pub struct SerialState {
interrupt_enable: u8,
interrupt_identification: u8,
line_control: u8,
line_status: u8,
modem_control: u8,
modem_status: u8,
scratch: u8,
baud_divisor: u16,
in_buffer: VecDeque<u8>,
}
impl Serial { impl Serial {
pub fn new( pub fn new(
id: String,
interrupt: Arc<Box<dyn InterruptSourceGroup>>, interrupt: Arc<Box<dyn InterruptSourceGroup>>,
out: Option<Box<dyn io::Write + Send>>, out: Option<Box<dyn io::Write + Send>>,
) -> Serial { ) -> Serial {
Serial { Serial {
id,
interrupt_enable: 0, interrupt_enable: 0,
interrupt_identification: DEFAULT_INTERRUPT_IDENTIFICATION, interrupt_identification: DEFAULT_INTERRUPT_IDENTIFICATION,
interrupt, interrupt,
@@ -90,15 +111,16 @@ impl Serial {
/// Constructs a Serial port ready for output. /// Constructs a Serial port ready for output.
pub fn new_out( pub fn new_out(
id: String,
interrupt: Arc<Box<dyn InterruptSourceGroup>>, interrupt: Arc<Box<dyn InterruptSourceGroup>>,
out: Box<dyn io::Write + Send>, out: Box<dyn io::Write + Send>,
) -> Serial { ) -> Serial {
Self::new(interrupt, Some(out)) Self::new(id, interrupt, Some(out))
} }
/// Constructs a Serial port with no connected output. /// Constructs a Serial port with no connected output.
pub fn new_sink(interrupt: Arc<Box<dyn InterruptSourceGroup>>) -> Serial { pub fn new_sink(id: String, interrupt: Arc<Box<dyn InterruptSourceGroup>>) -> Serial {
Self::new(interrupt, None) Self::new(id, interrupt, None)
} }
/// Queues raw bytes for the guest to read and signals the interrupt if the line status would /// Queues raw bytes for the guest to read and signals the interrupt if the line status would
@@ -194,6 +216,32 @@ impl Serial {
} }
Ok(()) Ok(())
} }
fn state(&self) -> SerialState {
SerialState {
interrupt_enable: self.interrupt_enable,
interrupt_identification: self.interrupt_identification,
line_control: self.line_control,
line_status: self.line_status,
modem_control: self.modem_control,
modem_status: self.modem_status,
scratch: self.scratch,
baud_divisor: self.baud_divisor,
in_buffer: self.in_buffer.clone(),
}
}
fn set_state(&mut self, state: &SerialState) {
self.interrupt_enable = state.interrupt_enable;
self.interrupt_identification = state.interrupt_identification;
self.line_control = state.line_control;
self.line_status = state.line_status;
self.modem_control = state.modem_control;
self.modem_status = state.modem_status;
self.scratch = state.scratch;
self.baud_divisor = state.baud_divisor;
self.in_buffer = state.in_buffer.clone();
}
} }
impl BusDevice for Serial { impl BusDevice for Serial {
@@ -236,6 +284,51 @@ impl BusDevice for Serial {
} }
} }
impl Snapshottable for Serial {
fn id(&self) -> String {
self.id.clone()
}
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
let snapshot =
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
let mut serial_snapshot = Snapshot::new(self.id.as_str());
serial_snapshot.add_data_section(SnapshotDataSection {
id: format!("{}-section", self.id),
snapshot,
});
Ok(serial_snapshot)
}
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
if let Some(serial_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
let serial_state = match serde_json::from_slice(&serial_section.snapshot) {
Ok(state) => state,
Err(error) => {
return Err(MigratableError::Restore(anyhow!(
"Could not deserialize SERIAL {}",
error
)))
}
};
self.set_state(&serial_state);
return Ok(());
}
Err(MigratableError::Restore(anyhow!(
"Could not find the serial snapshot section"
)))
}
}
impl Pausable for Serial {}
impl Transportable for Serial {}
impl Migratable for Serial {}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -244,6 +337,8 @@ mod tests {
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig}; use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::eventfd::EventFd;
const SERIAL_NAME: &str = "serial";
struct TestInterrupt { struct TestInterrupt {
event_fd: EventFd, event_fd: EventFd,
} }
@@ -294,6 +389,7 @@ mod tests {
let intr_evt = EventFd::new(0).unwrap(); let intr_evt = EventFd::new(0).unwrap();
let serial_out = SharedBuffer::new(); let serial_out = SharedBuffer::new();
let mut serial = Serial::new_out( let mut serial = Serial::new_out(
String::from(SERIAL_NAME),
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))), Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
Box::new(serial_out.clone()), Box::new(serial_out.clone()),
); );
@@ -313,6 +409,7 @@ mod tests {
let intr_evt = EventFd::new(0).unwrap(); let intr_evt = EventFd::new(0).unwrap();
let serial_out = SharedBuffer::new(); let serial_out = SharedBuffer::new();
let mut serial = Serial::new_out( let mut serial = Serial::new_out(
String::from(SERIAL_NAME),
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))), Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
Box::new(serial_out.clone()), Box::new(serial_out.clone()),
); );
@@ -350,9 +447,10 @@ mod tests {
#[test] #[test]
fn serial_thr() { fn serial_thr() {
let intr_evt = EventFd::new(0).unwrap(); let intr_evt = EventFd::new(0).unwrap();
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new( let mut serial = Serial::new_sink(
intr_evt.try_clone().unwrap(), String::from(SERIAL_NAME),
)))); Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
);
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd // write 1 to the interrupt event fd, so that read doesn't block in case the event fd
// counter doesn't change (for 0 it blocks) // counter doesn't change (for 0 it blocks)
@@ -371,9 +469,10 @@ mod tests {
#[test] #[test]
fn serial_dlab() { fn serial_dlab() {
let intr_evt = EventFd::new(0).unwrap(); let intr_evt = EventFd::new(0).unwrap();
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new( let mut serial = Serial::new_sink(
intr_evt.try_clone().unwrap(), String::from(SERIAL_NAME),
)))); Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
);
serial.write(0, LCR as u64, &[LCR_DLAB_BIT as u8]); serial.write(0, LCR as u64, &[LCR_DLAB_BIT as u8]);
serial.write(0, DLAB_LOW as u64, &[0x12 as u8]); serial.write(0, DLAB_LOW as u64, &[0x12 as u8]);
@@ -391,9 +490,10 @@ mod tests {
#[test] #[test]
fn serial_modem() { fn serial_modem() {
let intr_evt = EventFd::new(0).unwrap(); let intr_evt = EventFd::new(0).unwrap();
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new( let mut serial = Serial::new_sink(
intr_evt.try_clone().unwrap(), String::from(SERIAL_NAME),
)))); Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
);
serial.write(0, MCR as u64, &[MCR_LOOP_BIT as u8]); serial.write(0, MCR as u64, &[MCR_LOOP_BIT as u8]);
serial.write(0, DATA as u64, &['a' as u8]); serial.write(0, DATA as u64, &['a' as u8]);
@@ -416,9 +516,10 @@ mod tests {
#[test] #[test]
fn serial_scratch() { fn serial_scratch() {
let intr_evt = EventFd::new(0).unwrap(); let intr_evt = EventFd::new(0).unwrap();
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new( let mut serial = Serial::new_sink(
intr_evt.try_clone().unwrap(), String::from(SERIAL_NAME),
)))); Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
);
serial.write(0, SCR as u64, &[0x12 as u8]); serial.write(0, SCR as u64, &[0x12 as u8]);

View File

@@ -6,6 +6,7 @@
// found in the LICENSE-BSD-3-Clause file. // found in the LICENSE-BSD-3-Clause file.
//! Emulates virtual and hardware devices. //! Emulates virtual and hardware devices.
extern crate anyhow;
#[macro_use] #[macro_use]
extern crate bitflags; extern crate bitflags;
extern crate byteorder; extern crate byteorder;
@@ -15,9 +16,14 @@ extern crate libc;
extern crate log; extern crate log;
#[cfg(feature = "acpi")] #[cfg(feature = "acpi")]
extern crate acpi_tables; extern crate acpi_tables;
extern crate serde;
extern crate vm_device; extern crate vm_device;
extern crate vm_memory; extern crate vm_memory;
extern crate vm_migration;
extern crate vmm_sys_util; extern crate vmm_sys_util;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
use std::fs::File; use std::fs::File;
use std::io; use std::io;
@@ -25,6 +31,10 @@ use std::io;
#[cfg(feature = "acpi")] #[cfg(feature = "acpi")]
mod acpi; mod acpi;
mod bus; mod bus;
#[cfg(target_arch = "aarch64")]
pub mod gic;
pub mod interrupt_controller;
#[cfg(target_arch = "x86_64")]
pub mod ioapic; pub mod ioapic;
pub mod legacy; pub mod legacy;

View File

@@ -81,8 +81,8 @@ Shut the VMM down | `/vmm.shutdown` | N/A | N/A
Action | Endpoint | Request Body | Response Body | Prerequisites Action | Endpoint | Request Body | Response Body | Prerequisites
-----------------------------------|---------------------|---------------------------|-------------------|--------------------------- -----------------------------------|---------------------|---------------------------|-------------------|---------------------------
Create the VM | `/vm.create` | `/schemas/VmConfig` | N/A | The VM is not created yet Create the VM | `/vm.create` | `/schemas/VmConfig` | N/A | The VM is not created yet
Delete the VM | `/vm.delete` | N/A | N/A | The VM is created but not booted Delete the VM | `/vm.delete` | N/A | N/A | N/A
Boot the VM | `/vm.boot` | N/A | N/A | The VM is created Boot the VM | `/vm.boot` | N/A | N/A | The VM is created but not booted
Shut the VM down | `/vm.shutdown` | N/A | N/A | The VM is booted Shut the VM down | `/vm.shutdown` | N/A | N/A | The VM is booted
Reboot the VM | `/vm.reboot` | N/A | N/A | The VM is booted Reboot the VM | `/vm.reboot` | N/A | N/A | The VM is booted
Pause the VM | `/vm.pause` | N/A | N/A | The VM is booted Pause the VM | `/vm.pause` | N/A | N/A | The VM is booted
@@ -92,6 +92,11 @@ Remove memory from the VM | `/vm.resize` | `/schemas/VmResize`
Dump the VM information | `/vm.info` | N/A | `/schemas/VmInfo` | The VM is created Dump the VM information | `/vm.info` | N/A | `/schemas/VmInfo` | The VM is created
Add VFIO PCI device to the VM | `/vm.add-device` | `/schemas/VmAddDevice` | N/A | The VM is booted Add VFIO PCI device to the VM | `/vm.add-device` | `/schemas/VmAddDevice` | N/A | The VM is booted
Remove VFIO PCI device from the VM | `/vm.remove-device` | `/schemas/VmRemoveDevice` | N/A | The VM is booted Remove VFIO PCI device from the VM | `/vm.remove-device` | `/schemas/VmRemoveDevice` | N/A | The VM is booted
Add disk device to the VM | `/vm.add-disk` | `/schemas/DiskConfig` | N/A | The VM is booted
Add fs device to the VM | `/vm.add-fs` | `/schemas/FsConfig` | N/A | The VM is booted
Add pmem device to the VM | `/vm.add-pmem` | `/schemas/PmemConfig` | N/A | The VM is booted
Add network device to the VM | `/vm.add-net` | `/schemas/NetConfig` | N/A | The VM is booted
Add vsock device to the VM | `/vm.add-vsock` | `/schemas/VsockConfig` | N/A | The VM is booted
### REST API Examples ### REST API Examples
@@ -370,4 +375,3 @@ APIs work together, let's look at a complete VM creation flow, from the
user. This is abstracted by the user. This is abstracted by the
[micro_http](https://github.com/firecracker-microvm/firecracker/tree/master/src/micro_http) [micro_http](https://github.com/firecracker-microvm/firecracker/tree/master/src/micro_http)
crate. crate.

64
docs/arm64.md Normal file
View File

@@ -0,0 +1,64 @@
# How to build and run Cloud-hypervisor on Arm64
Cloud-hypervisor is partially enabled on AArch64 architecture.
Although all features are not ready yet, you can begin to test Cloud-hypervisor on a Arm64 host by following this guide.
## Prerequisites
On Arm64 machines, Cloud-hypervisor depends on an external library `libfdt-dev` for generating Flatted Device Tree (FDT).
The long-term plan is to replace `libfdt-dev` with some pure-Rust component to get rid of such dependency.
```bash
sudo apt-get update
sudo apt-get install libfdt-dev
```
## Build
Before building, a hack trick need to be performed to get rid of some build error in vmm component. See [this](https://github.com/cloud-hypervisor/kvm-bindings/pull/1) for more info about this temporary workaround.
```bash
sed -i 's/"with-serde",\ //g' vmm/Cargo.toml
```
The support of AArch64 is in very early stage, only Virtio devices with MMIO tranport is available.
```bash
cargo build --no-default-features --features "mmio"
```
## Image
Download kernel binary and rootfs image from AWS.
```bash
wget https://s3.amazonaws.com/spec.ccfc.min/img/aarch64/ubuntu_with_ssh/fsfiles/xenial.rootfs.ext4 -O rootfs.img
wget https://s3.amazonaws.com/spec.ccfc.min/img/aarch64/ubuntu_with_ssh/kernel/vmlinux.bin -O kernel.bin
```
## Containerized build
If you want to build and test Cloud Hypervisor without having to install all the required dependencies, you can also turn to the development script: dev_cli.sh.
To build the development container:
```bash
./scripts/dev_cli.sh build-container
```
To build Cloud-hypervisor in the container:
```bash
./scripts/dev_cli.sh build
```
## Run
Assuming you have built Cloud-hypervisor with the development container, a VM can be started with command:
```bash
sudo target/debug/cloud-hypervisor --kernel kernel.bin --disk path=rootfs.ext4 --cmdline "keep_bootcon console=hvc0 reboot=k panic=1 pci=off root=/dev/vda rw" --cpus boot=4 --memory size=512M --seccomp false --serial file=serial.log --log-file log.log -vvv
```
If the build was done out of the container, replace the binary path with `build/cargo_target/aarch64-unknown-linux-gnu/debug/cloud-hypervisor`.

View File

@@ -60,20 +60,20 @@ Assuming you have `clear-kvm.img` and `custom-vmlinux.bin` on your system, here
--disk path=clear-kvm.img \ --disk path=clear-kvm.img \
--kernel custom-vmlinux.bin \ --kernel custom-vmlinux.bin \
--cmdline "console=ttyS0 reboot=k panic=1 nomodules root=/dev/vda3" \ --cmdline "console=ttyS0 reboot=k panic=1 nomodules root=/dev/vda3" \
--fs tag=myfs,sock=/tmp/virtiofs,num_queues=1,queue_size=512 --fs tag=myfs,socket=/tmp/virtiofs,num_queues=1,queue_size=512
``` ```
By default, DAX is enabled with a cache window of 8GiB. You can specify a custom size (let's say 4GiB for this example) for the cache by explicitly setting DAX and the cache size: By default, DAX is enabled with a cache window of 8GiB. You can specify a custom size (let's say 4GiB for this example) for the cache by explicitly setting DAX and the cache size:
```bash ```bash
--fs tag=virtiofs,sock=/tmp/virtiofs,num_queues=1,queue_size=512,dax=on,cache_size=4G --fs tag=virtiofs,socket=/tmp/virtiofs,num_queues=1,queue_size=512,dax=on,cache_size=4G
``` ```
In case you don't want to use a shared window of cache to pass the shared files content, this means you will have to explicitly disable DAX with `dax=off`. Note that in this case, the `cache_size` parameter will be ignored. In case you don't want to use a shared window of cache to pass the shared files content, this means you will have to explicitly disable DAX with `dax=off`. Note that in this case, the `cache_size` parameter will be ignored.
```bash ```bash
--fs tag=virtiofs,sock=/tmp/virtiofs,num_queues=1,queue_size=512,dax=off --fs tag=virtiofs,socket=/tmp/virtiofs,num_queues=1,queue_size=512,dax=off
``` ```

View File

@@ -0,0 +1,104 @@
# How to test vhost-user-blk with SPDK
The purpose of this document is to illustrate how to test vhost-user-blk in Cloud Hypervisor with SPDK as the backend.
## Framework
It's a simple test to validate the block read/write between VM and block backend.
```
+----+----------+ +-------------+-----------+
| | | | | |
| |vhost-user|----------| vhost-user | dpdk |
| |blk device| | port 1 | |
| | | | | |
| +----------+ +-------------+-----------+
| | | |
| vm | | spdk |
| | | |
+--+----------------------------------------------------+--+
| | hugepages | |
| +----------------------------------------------------+ |
| |
| host |
| |
+----------------------------------------------------------+
```
## Prerequisites
Prior to running the test, the following steps need to be performed.
- Enable hugepages
- Install SPDK
Here are some good references for detailing them.
- spdk
* https://spdk.io/doc/
## Test environment
The below test environment is based on ubuntu release(16.04.1 LTS), as for other system, please check related document.
The test runs with multiple queue (MQ) support enabled, using 4 queues defined for both SPDK and the virtual machine.
Here are the details on how the test can be run.
### The hugepages settings in host linux
Add "default_hugepagesz=1G hugepagesz=1G hugepages=2" into host linux cmdline.
As for how to change Ubuntu linux cmdline in grub file, please ref below link:
https://www.ostechnix.com/configure-grub-2-boot-loader-settings-ubuntu-16-04/
reboot Ubuntu
sudo mount -t hugetlbfs -o pagesize=1G none /dev/hugepages
### Download the SPDK code
git clone https://github.com/spdk/spdk
cd spdk
git submodule update --init
### Create the build dep
./scripts/pkgdep.sh
### Build spdk
./configure
make
### Set the SPDk environment
sudo HUGEMEM=2048 scripts/setup.sh
sudo ./app/vhost/vhost -S /var/tmp -s 1024 -m 0x3 &
### Create 512M block device
sudo scripts/rpc.py bdev_malloc_create 512 512 -b Malloc0
sudo scripts/rpc.py vhost_create_blk_controller --cpumask 0x1 vhost.1 Malloc0
_Launch the VM_
VMs run in client mode. They connect to the socket created by the `dpdkvhostuser` in the SPDK backend.
```bash
# From the test terminal. We need to create one vhost-user-blk device for the --disk.
./cloud-hypervisor \
--cpus boot=4 \
--memory size=1024M,file=/dev/hugepages \
--kernel linux/arch/x86/boot/compressed/vmlinux.bin \
--cmdline "console=ttyS0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3 iommu=off" \
--disk "path=images/clear-kvm.img" "num_queues=4,queue_size=128,vhost_user=true,socket=/var/tmp/vhost.1" \
--console off \
--serial tty \
--rng
```
```bash
# How to test the vhost-user-blk device with SPDK backend
login in guest
# Use lsblk command to find out vhost-user-blk device
lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
vda 253:0 0 8.5G 0 disk
├─vda1 253:1 0 511M 0 part
├─vda2 253:2 0 32M 0 part [SWAP]
└─vda3 253:3 0 8G 0 part /
vdb 253:16 0 512M 0 disk
The vhost-user-blk device is /dev/vdb
# How to do simple read/write test
dd if=/dev/vdb of=/dev/null bs=2M iflag=direct
dd of=/dev/vdb if=/dev/zero bs=2M oflag=direct count=256
If you want to do fio test, please install fio binary into guest. The detailed info is not listed here.

View File

@@ -4,14 +4,14 @@ version = "0.1.0"
authors = ["The Chromium OS Authors"] authors = ["The Chromium OS Authors"]
[dependencies] [dependencies]
libc = "0.2.68" libc = "0.2.71"
rand = "0.7.3" rand = "0.7.3"
serde = "1.0.105" serde = "1.0.111"
vmm-sys-util = ">=0.3.1" vmm-sys-util = ">=0.3.1"
net_gen = { path = "../net_gen" } net_gen = { path = "../net_gen" }
[dev-dependencies] [dev-dependencies]
lazy_static = "1.3.0" lazy_static = "1.3.0"
pnet = "0.25.0" pnet = "0.26.0"
serde_json = "1.0.48" serde_json = "1.0.55"

View File

@@ -9,6 +9,7 @@ use rand::Rng;
use std::fmt; use std::fmt;
use std::io; use std::io;
use std::result::Result; use std::result::Result;
use std::str::FromStr;
use serde::de::{Deserialize, Deserializer, Error}; use serde::de::{Deserialize, Deserializer, Error};
use serde::ser::{Serialize, Serializer}; use serde::ser::{Serialize, Serializer};
@@ -120,6 +121,18 @@ impl<'de> Deserialize<'de> for MacAddr {
} }
} }
pub enum MacAddrParseError {
InvalidValue(String),
}
impl FromStr for MacAddr {
type Err = MacAddrParseError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
MacAddr::parse_str(s).map_err(|_| MacAddrParseError::InvalidValue(s.to_owned()))
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
extern crate serde_json; extern crate serde_json;

View File

@@ -5,15 +5,14 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the THIRD-PARTY file. // found in the THIRD-PARTY file.
use super::{create_sockaddr, create_socket, Error as NetUtilError, MacAddr};
use mac::MAC_ADDR_LEN;
use net_gen;
use std::fs::File; use std::fs::File;
use std::io::{Error as IoError, Read, Result as IoResult, Write}; use std::io::{Error as IoError, Read, Result as IoResult, Write};
use std::net; use std::net;
use std::os::raw::*; use std::os::raw::*;
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use super::{create_sockaddr, create_socket, Error as NetUtilError};
use libc;
use net_gen;
use vmm_sys_util::ioctl::{ioctl_with_mut_ref, ioctl_with_ref, ioctl_with_val}; use vmm_sys_util::ioctl::{ioctl_with_mut_ref, ioctl_with_ref, ioctl_with_val};
#[derive(Debug)] #[derive(Debug)]
@@ -31,6 +30,8 @@ pub enum Error {
/// Failed to create a socket. /// Failed to create a socket.
NetUtil(NetUtilError), NetUtil(NetUtilError),
InvalidIfname, InvalidIfname,
/// Error parsing MAC data
MacParsing(()),
} }
pub type Result<T> = ::std::result::Result<T, Error>; pub type Result<T> = ::std::result::Result<T, Error>;
@@ -176,6 +177,72 @@ impl Tap {
Ok(()) Ok(())
} }
/// Set mac addr for tap interface.
pub fn set_mac_addr(&self, addr: MacAddr) -> Result<()> {
// Checking if the mac address already matches the desired one
// is useful to avoid making the "set ioctl" in the case where
// the VMM is running without the privilege to do that.
// In practice this comes from a reboot after the configuration
// has been update with the kernel generated address.
if self.get_mac_addr()? == addr {
return Ok(());
}
let sock = create_socket().map_err(Error::NetUtil)?;
let mut ifreq = self.get_ifreq();
// ioctl is safe. Called with a valid sock fd, and we check the return.
#[allow(clippy::cast_lossless)]
let ret =
unsafe { ioctl_with_ref(&sock, net_gen::sockios::SIOCGIFHWADDR as c_ulong, &ifreq) };
if ret < 0 {
return Err(Error::IoctlError(IoError::last_os_error()));
}
// We only access one field of the ifru union, hence this is safe.
unsafe {
let ifru_hwaddr = ifreq.ifr_ifru.ifru_hwaddr.as_mut();
for (i, v) in addr.get_bytes().iter().enumerate() {
ifru_hwaddr.sa_data[i] = *v as c_char;
}
}
// ioctl is safe. Called with a valid sock fd, and we check the return.
#[allow(clippy::cast_lossless)]
let ret =
unsafe { ioctl_with_ref(&sock, net_gen::sockios::SIOCSIFHWADDR as c_ulong, &ifreq) };
if ret < 0 {
return Err(Error::IoctlError(IoError::last_os_error()));
}
Ok(())
}
/// Get mac addr for tap interface.
pub fn get_mac_addr(&self) -> Result<MacAddr> {
let sock = create_socket().map_err(Error::NetUtil)?;
let ifreq = self.get_ifreq();
// ioctl is safe. Called with a valid sock fd, and we check the return.
#[allow(clippy::cast_lossless)]
let ret =
unsafe { ioctl_with_ref(&sock, net_gen::sockios::SIOCGIFHWADDR as c_ulong, &ifreq) };
if ret < 0 {
return Err(Error::IoctlError(IoError::last_os_error()));
}
// We only access one field of the ifru union, hence this is safe.
let addr = unsafe {
let ifru_hwaddr = ifreq.ifr_ifru.ifru_hwaddr.as_ref();
MacAddr::from_bytes(
&*(&ifru_hwaddr.sa_data[0..MAC_ADDR_LEN] as *const _ as *const [u8]),
)
.map_err(Error::MacParsing)?
};
Ok(addr)
}
/// Set the netmask for the subnet that the tap interface will exist on. /// Set the netmask for the subnet that the tap interface will exist on.
pub fn set_netmask(&self, netmask: net::Ipv4Addr) -> Result<()> { pub fn set_netmask(&self, netmask: net::Ipv4Addr) -> Result<()> {
let sock = create_socket().map_err(Error::NetUtil)?; let sock = create_socket().map_err(Error::NetUtil)?;
@@ -219,9 +286,26 @@ impl Tap {
let mut ifreq = self.get_ifreq(); let mut ifreq = self.get_ifreq();
#[allow(clippy::cast_lossless)]
let ret =
unsafe { ioctl_with_ref(&sock, net_gen::sockios::SIOCGIFFLAGS as c_ulong, &ifreq) };
if ret < 0 {
return Err(Error::IoctlError(IoError::last_os_error()));
}
// If TAP device is already up don't try and enable it
let ifru_flags = unsafe { ifreq.ifr_ifru.ifru_flags.as_ref() };
if ifru_flags
& (net_gen::net_device_flags_IFF_UP | net_gen::net_device_flags_IFF_RUNNING) as i16
== (net_gen::net_device_flags_IFF_UP | net_gen::net_device_flags_IFF_RUNNING) as i16
{
return Ok(());
}
// We only access one field of the ifru union, hence this is safe. // We only access one field of the ifru union, hence this is safe.
unsafe { unsafe {
let ifru_flags = ifreq.ifr_ifru.ifru_flags.as_mut(); let ifru_flags = ifreq.ifr_ifru.ifru_flags.as_mut();
*ifru_flags = *ifru_flags =
(net_gen::net_device_flags_IFF_UP | net_gen::net_device_flags_IFF_RUNNING) as i16; (net_gen::net_device_flags_IFF_UP | net_gen::net_device_flags_IFF_RUNNING) as i16;
} }
@@ -443,7 +527,7 @@ mod tests {
let interface = interfaces.into_iter().find(interface_name_matches).unwrap(); let interface = interfaces.into_iter().find(interface_name_matches).unwrap();
if let Ok(Ethernet(tx, rx)) = datalink::channel(&interface, Default::default()) { if let Ok(Ethernet(tx, rx)) = datalink::channel(&interface, Default::default()) {
(interface.mac_address(), tx, rx) (interface.mac.unwrap(), tx, rx)
} else { } else {
panic!("datalink channel error or unhandled channel type"); panic!("datalink channel error or unhandled channel type");
} }

View File

@@ -5,10 +5,23 @@ authors = ["Samuel Ortiz <sameo@linux.intel.com>"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
vm-allocator = { path = "../vm-allocator" } anyhow = "1.0"
byteorder = "1.3.4" byteorder = "1.3.4"
devices = { path = "../devices" } devices = { path = "../devices" }
libc = "0.2.68" kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch" }
kvm-ioctls = { git = "https://github.com/cloud-hypervisor/kvm-ioctls", branch = "ch" }
vfio-ioctls = { git = "https://github.com/cloud-hypervisor/vfio-ioctls", branch = "ch" }
vmm-sys-util = ">=0.3.1"
libc = "0.2.71"
log = "0.4.8" log = "0.4.8"
serde = {version = ">=1.0.27", features = ["rc"] }
serde_derive = ">=1.0.27"
serde_json = ">=1.0.9"
vm-allocator = { path = "../vm-allocator" }
vm-device = { path = "../vm-device" } vm-device = { path = "../vm-device" }
vm-memory = "0.1.0" vm-memory = "0.2.1"
vm-migration = { path = "../vm-migration" }
[dependencies.vfio-bindings]
git = "https://github.com/rust-vmm/vfio-bindings"
features = ["fam-wrappers"]

View File

@@ -8,7 +8,6 @@ use crate::configuration::{
use crate::device::{DeviceRelocation, Error as PciDeviceError, PciDevice}; use crate::device::{DeviceRelocation, Error as PciDeviceError, PciDevice};
use byteorder::{ByteOrder, LittleEndian}; use byteorder::{ByteOrder, LittleEndian};
use devices::BusDevice; use devices::BusDevice;
use std;
use std::any::Any; use std::any::Any;
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::DerefMut; use std::ops::DerefMut;
@@ -34,6 +33,8 @@ pub enum PciRootError {
NoPciDeviceSlotAvailable, NoPciDeviceSlotAvailable,
/// Invalid PCI device identifier provided. /// Invalid PCI device identifier provided.
InvalidPciDeviceSlot(usize), InvalidPciDeviceSlot(usize),
/// Valid PCI device identifier but already used.
AlreadyInUsePciDeviceSlot(usize),
} }
pub type Result<T> = std::result::Result<T, PciRootError>; pub type Result<T> = std::result::Result<T, PciRootError>;
@@ -53,6 +54,7 @@ impl PciRoot {
config: PciConfiguration::new( config: PciConfiguration::new(
VENDOR_ID_INTEL, VENDOR_ID_INTEL,
DEVICE_ID_INTEL_VIRT_PCIE_HOST, DEVICE_ID_INTEL_VIRT_PCIE_HOST,
0,
PciClassCode::BridgeDevice, PciClassCode::BridgeDevice,
&PciBridgeSubclass::HostBridge, &PciBridgeSubclass::HostBridge,
None, None,
@@ -154,6 +156,19 @@ impl PciBus {
Err(PciRootError::NoPciDeviceSlotAvailable) Err(PciRootError::NoPciDeviceSlotAvailable)
} }
pub fn get_device_id(&mut self, id: usize) -> Result<()> {
if id < NUM_DEVICE_IDS {
if !self.device_ids[id] {
self.device_ids[id] = true;
Ok(())
} else {
Err(PciRootError::AlreadyInUsePciDeviceSlot(id))
}
} else {
Err(PciRootError::InvalidPciDeviceSlot(id))
}
}
pub fn put_device_id(&mut self, id: usize) -> Result<()> { pub fn put_device_id(&mut self, id: usize) -> Result<()> {
if id < NUM_DEVICE_IDS { if id < NUM_DEVICE_IDS {
self.device_ids[id] = false; self.device_ids[id] = false;
@@ -239,7 +254,10 @@ impl PciConfigIo {
device.deref_mut(), device.deref_mut(),
params.region_type, params.region_type,
) { ) {
error!("Failed moving device BAR: {}", e); error!(
"Failed moving device BAR: {}: 0x{:x}->0x{:x}(0x{:x})",
e, params.old_base, params.new_base, params.len
);
} }
} }
@@ -355,7 +373,10 @@ impl PciConfigMmio {
device.deref_mut(), device.deref_mut(),
params.region_type, params.region_type,
) { ) {
error!("Failed moving device BAR: {}", e); error!(
"Failed moving device BAR: {}: 0x{:x}->0x{:x}(0x{:x})",
e, params.old_base, params.new_base, params.len
);
} }
} }

186
pci/src/configuration.rs Executable file → Normal file
View File

@@ -2,12 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file. // found in the LICENSE-BSD-3-Clause file.
use std::sync::{Arc, Mutex};
use crate::device::BarReprogrammingParams; use crate::device::BarReprogrammingParams;
use crate::{MsixConfig, PciInterruptPin}; use crate::{MsixConfig, PciInterruptPin};
use anyhow::anyhow;
use byteorder::{ByteOrder, LittleEndian}; use byteorder::{ByteOrder, LittleEndian};
use std::fmt::{self, Display}; use std::fmt::{self, Display};
use std::sync::{Arc, Mutex};
use vm_migration::{MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable};
// The number of 32bit registers in the config space, 4096 bytes. // The number of 32bit registers in the config space, 4096 bytes.
const NUM_CONFIGURATION_REGISTERS: usize = 1024; const NUM_CONFIGURATION_REGISTERS: usize = 1024;
@@ -244,6 +245,53 @@ pub trait PciCapability {
fn id(&self) -> PciCapabilityID; fn id(&self) -> PciCapabilityID;
} }
fn encode_32_bits_bar_size(bar_size: u32) -> Option<u32> {
if bar_size > 0 {
return Some(!(bar_size - 1));
}
None
}
fn decode_32_bits_bar_size(bar_size: u32) -> Option<u32> {
if bar_size > 0 {
return Some(!bar_size + 1);
}
None
}
fn encode_64_bits_bar_size(bar_size: u64) -> Option<(u32, u32)> {
if bar_size > 0 {
let result = !(bar_size - 1);
let result_hi = (result >> 32) as u32;
let result_lo = (result & 0xffff_ffff) as u32;
return Some((result_hi, result_lo));
}
None
}
fn decode_64_bits_bar_size(bar_size_hi: u32, bar_size_lo: u32) -> Option<u64> {
let bar_size: u64 = ((bar_size_hi as u64) << 32) | (bar_size_lo as u64);
if bar_size > 0 {
return Some(!bar_size + 1);
}
None
}
#[derive(Serialize, Deserialize)]
struct PciConfigurationState {
registers: Vec<u32>,
writable_bits: Vec<u32>,
bar_addr: Vec<u32>,
bar_size: Vec<u32>,
bar_used: Vec<bool>,
bar_type: Vec<Option<PciBarRegionType>>,
rom_bar_addr: u32,
rom_bar_size: u32,
rom_bar_used: bool,
last_capability: Option<(usize, usize)>,
msix_cap_reg_idx: Option<usize>,
}
/// Contains the configuration space of a PCI node. /// Contains the configuration space of a PCI node.
/// See the [specification](https://en.wikipedia.org/wiki/PCI_configuration_space). /// See the [specification](https://en.wikipedia.org/wiki/PCI_configuration_space).
/// The configuration space is accessed with DWORD reads and writes from the guest. /// The configuration space is accessed with DWORD reads and writes from the guest.
@@ -264,7 +312,7 @@ pub struct PciConfiguration {
} }
/// See pci_regs.h in kernel /// See pci_regs.h in kernel
#[derive(Copy, Clone, PartialEq)] #[derive(Copy, Clone, PartialEq, Serialize, Deserialize)]
pub enum PciBarRegionType { pub enum PciBarRegionType {
Memory32BitRegion = 0, Memory32BitRegion = 0,
IORegion = 0x01, IORegion = 0x01,
@@ -297,6 +345,10 @@ pub enum Error {
CapabilityEmpty, CapabilityEmpty,
CapabilityLengthInvalid(usize), CapabilityLengthInvalid(usize),
CapabilitySpaceFull(usize), CapabilitySpaceFull(usize),
Decode32BarSize,
Decode64BarSize,
Encode32BarSize,
Encode64BarSize,
RomBarAddressInvalid(u64, u64), RomBarAddressInvalid(u64, u64),
RomBarInUse(usize), RomBarInUse(usize),
RomBarInvalid(usize), RomBarInvalid(usize),
@@ -324,6 +376,10 @@ impl Display for Error {
CapabilityEmpty => write!(f, "empty capabilities are invalid"), CapabilityEmpty => write!(f, "empty capabilities are invalid"),
CapabilityLengthInvalid(l) => write!(f, "Invalid capability length {}", l), CapabilityLengthInvalid(l) => write!(f, "Invalid capability length {}", l),
CapabilitySpaceFull(s) => write!(f, "capability of size {} doesn't fit", s), CapabilitySpaceFull(s) => write!(f, "capability of size {} doesn't fit", s),
Decode32BarSize => write!(f, "failed to decode 32 bits BAR size"),
Decode64BarSize => write!(f, "failed to decode 64 bits BAR size"),
Encode32BarSize => write!(f, "failed to encode 32 bits BAR size"),
Encode64BarSize => write!(f, "failed to encode 64 bits BAR size"),
RomBarAddressInvalid(a, s) => write!(f, "address {} size {} too big", a, s), RomBarAddressInvalid(a, s) => write!(f, "address {} size {} too big", a, s),
RomBarInUse(b) => write!(f, "rom bar {} already used", b), RomBarInUse(b) => write!(f, "rom bar {} already used", b),
RomBarInvalid(b) => write!(f, "rom bar {} invalid, max {}", b, NUM_BAR_REGS - 1), RomBarInvalid(b) => write!(f, "rom bar {} invalid, max {}", b, NUM_BAR_REGS - 1),
@@ -337,6 +393,7 @@ impl PciConfiguration {
pub fn new( pub fn new(
vendor_id: u16, vendor_id: u16,
device_id: u16, device_id: u16,
revision_id: u8,
class_code: PciClassCode, class_code: PciClassCode,
subclass: &dyn PciSubclass, subclass: &dyn PciSubclass,
programming_interface: Option<&dyn PciProgrammingInterface>, programming_interface: Option<&dyn PciProgrammingInterface>,
@@ -359,7 +416,8 @@ impl PciConfiguration {
}; };
registers[2] = u32::from(class_code.get_register_value()) << 24 registers[2] = u32::from(class_code.get_register_value()) << 24
| u32::from(subclass.get_register_value()) << 16 | u32::from(subclass.get_register_value()) << 16
| u32::from(pi) << 8; | u32::from(pi) << 8
| u32::from(revision_id);
writable_bits[3] = 0x0000_00ff; // Cacheline size (r/w) writable_bits[3] = 0x0000_00ff; // Cacheline size (r/w)
match header_type { match header_type {
PciHeaderType::Device => { PciHeaderType::Device => {
@@ -390,6 +448,37 @@ impl PciConfiguration {
} }
} }
fn state(&self) -> PciConfigurationState {
PciConfigurationState {
registers: self.registers.to_vec(),
writable_bits: self.writable_bits.to_vec(),
bar_addr: self.bar_addr.to_vec(),
bar_size: self.bar_size.to_vec(),
bar_used: self.bar_used.to_vec(),
bar_type: self.bar_type.to_vec(),
rom_bar_addr: self.rom_bar_addr,
rom_bar_size: self.rom_bar_size,
rom_bar_used: self.rom_bar_used,
last_capability: self.last_capability,
msix_cap_reg_idx: self.msix_cap_reg_idx,
}
}
fn set_state(&mut self, state: &PciConfigurationState) {
self.registers.clone_from_slice(state.registers.as_slice());
self.writable_bits
.clone_from_slice(state.writable_bits.as_slice());
self.bar_addr.clone_from_slice(state.bar_addr.as_slice());
self.bar_size.clone_from_slice(state.bar_size.as_slice());
self.bar_used.clone_from_slice(state.bar_used.as_slice());
self.bar_type.clone_from_slice(state.bar_type.as_slice());
self.rom_bar_addr = state.rom_bar_addr;
self.rom_bar_size = state.rom_bar_size;
self.rom_bar_used = state.rom_bar_used;
self.last_capability = state.last_capability;
self.msix_cap_reg_idx = state.msix_cap_reg_idx;
}
/// Reads a 32bit register from `reg_idx` in the register map. /// Reads a 32bit register from `reg_idx` in the register map.
pub fn read_reg(&self, reg_idx: usize) -> u32 { pub fn read_reg(&self, reg_idx: usize) -> u32 {
*(self.registers.get(reg_idx).unwrap_or(&0xffff_ffff)) *(self.registers.get(reg_idx).unwrap_or(&0xffff_ffff))
@@ -403,14 +492,14 @@ impl PciConfiguration {
// Handle very specific case where the BAR is being written with // Handle very specific case where the BAR is being written with
// all 1's to retrieve the BAR size during next BAR reading. // all 1's to retrieve the BAR size during next BAR reading.
if value == 0xffff_ffff { 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 { } else if reg_idx == ROM_BAR_REG {
// Handle very specific case where the BAR is being written with // Handle very specific case where the BAR is being written with
// all 1's on bits 31-11 to retrieve the BAR size during next BAR // all 1's on bits 31-11 to retrieve the BAR size during next BAR
// reading. // reading.
if value & ROM_BAR_ADDR_MASK == ROM_BAR_ADDR_MASK { if value & ROM_BAR_ADDR_MASK == ROM_BAR_ADDR_MASK {
mask = self.rom_bar_size; mask &= self.rom_bar_size;
} }
} }
@@ -494,6 +583,11 @@ impl PciConfiguration {
if end_addr > u64::from(u32::max_value()) { if end_addr > u64::from(u32::max_value()) {
return Err(Error::BarAddressInvalid(config.addr, config.size)); return Err(Error::BarAddressInvalid(config.addr, config.size));
} }
// Encode the BAR size as expected by the software running in
// the guest.
self.bar_size[config.reg_idx] =
encode_32_bits_bar_size(config.size as u32).ok_or(Error::Encode32BarSize)?;
} }
PciBarRegionType::Memory64BitRegion => { PciBarRegionType::Memory64BitRegion => {
if config.reg_idx + 1 >= NUM_BAR_REGS { if config.reg_idx + 1 >= NUM_BAR_REGS {
@@ -508,10 +602,16 @@ impl PciConfiguration {
return Err(Error::BarInUse64(config.reg_idx)); return Err(Error::BarInUse64(config.reg_idx));
} }
// Encode the BAR size as expected by the software running in
// the guest.
let (bar_size_hi, bar_size_lo) =
encode_64_bits_bar_size(config.size).ok_or(Error::Encode64BarSize)?;
self.registers[bar_idx + 1] = (config.addr >> 32) as u32; self.registers[bar_idx + 1] = (config.addr >> 32) as u32;
self.writable_bits[bar_idx + 1] = 0xffff_ffff; self.writable_bits[bar_idx + 1] = 0xffff_ffff;
self.bar_addr[config.reg_idx + 1] = self.registers[bar_idx + 1]; self.bar_addr[config.reg_idx + 1] = self.registers[bar_idx + 1];
self.bar_size[config.reg_idx + 1] = (config.size >> 32) as u32; self.bar_size[config.reg_idx] = bar_size_lo;
self.bar_size[config.reg_idx + 1] = bar_size_hi;
self.bar_used[config.reg_idx + 1] = true; self.bar_used[config.reg_idx + 1] = true;
} }
} }
@@ -527,7 +627,6 @@ impl PciConfiguration {
self.registers[bar_idx] = ((config.addr as u32) & mask) | lower_bits; self.registers[bar_idx] = ((config.addr as u32) & mask) | lower_bits;
self.writable_bits[bar_idx] = mask; self.writable_bits[bar_idx] = mask;
self.bar_addr[config.reg_idx] = self.registers[bar_idx]; self.bar_addr[config.reg_idx] = self.registers[bar_idx];
self.bar_size[config.reg_idx] = config.size as u32;
self.bar_used[config.reg_idx] = true; self.bar_used[config.reg_idx] = true;
self.bar_type[config.reg_idx] = Some(config.region_type); self.bar_type[config.reg_idx] = Some(config.region_type);
Ok(config.reg_idx) Ok(config.reg_idx)
@@ -559,7 +658,8 @@ impl PciConfiguration {
self.registers[config.reg_idx] = (config.addr as u32) | active; self.registers[config.reg_idx] = (config.addr as u32) | active;
self.writable_bits[config.reg_idx] = ROM_BAR_ADDR_MASK; self.writable_bits[config.reg_idx] = ROM_BAR_ADDR_MASK;
self.rom_bar_addr = self.registers[config.reg_idx]; self.rom_bar_addr = self.registers[config.reg_idx];
self.rom_bar_size = config.size as u32; self.rom_bar_size =
encode_32_bits_bar_size(config.size as u32).ok_or(Error::Encode32BarSize)?;
self.rom_bar_used = true; self.rom_bar_used = true;
Ok(config.reg_idx) Ok(config.reg_idx)
} }
@@ -697,7 +797,11 @@ impl PciConfiguration {
); );
let old_base = u64::from(self.bar_addr[bar_idx] & mask); let old_base = u64::from(self.bar_addr[bar_idx] & mask);
let new_base = u64::from(value & mask); let new_base = u64::from(value & mask);
let len = u64::from(self.bar_size[bar_idx]); let len = u64::from(
decode_32_bits_bar_size(self.bar_size[bar_idx])
.ok_or(Error::Decode32BarSize)
.unwrap(),
);
let region_type = bar_type; let region_type = bar_type;
self.bar_addr[bar_idx] = value; self.bar_addr[bar_idx] = value;
@@ -733,8 +837,10 @@ impl PciConfiguration {
| u64::from(self.bar_addr[bar_idx - 1] & self.writable_bits[reg_idx - 1]); | u64::from(self.bar_addr[bar_idx - 1] & self.writable_bits[reg_idx - 1]);
let new_base = u64::from(value & mask) << 32 let new_base = u64::from(value & mask) << 32
| u64::from(self.registers[reg_idx - 1] & self.writable_bits[reg_idx - 1]); | u64::from(self.registers[reg_idx - 1] & self.writable_bits[reg_idx - 1]);
let len = u64::from(self.bar_size[bar_idx]) << 32 let len =
| u64::from(self.bar_size[bar_idx - 1]); decode_64_bits_bar_size(self.bar_size[bar_idx], self.bar_size[bar_idx - 1])
.ok_or(Error::Decode64BarSize)
.unwrap();
let region_type = PciBarRegionType::Memory64BitRegion; let region_type = PciBarRegionType::Memory64BitRegion;
self.bar_addr[bar_idx] = value; self.bar_addr[bar_idx] = value;
@@ -760,7 +866,11 @@ impl PciConfiguration {
); );
let old_base = u64::from(self.rom_bar_addr & mask); let old_base = u64::from(self.rom_bar_addr & mask);
let new_base = u64::from(value & mask); let new_base = u64::from(value & mask);
let len = u64::from(self.rom_bar_size); let len = u64::from(
decode_32_bits_bar_size(self.rom_bar_size)
.ok_or(Error::Decode32BarSize)
.unwrap(),
);
let region_type = PciBarRegionType::Memory32BitRegion; let region_type = PciBarRegionType::Memory32BitRegion;
self.rom_bar_addr = value; self.rom_bar_addr = value;
@@ -777,6 +887,54 @@ impl PciConfiguration {
} }
} }
impl Pausable for PciConfiguration {}
impl Snapshottable for PciConfiguration {
fn id(&self) -> String {
String::from("pci_configuration")
}
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
let snapshot =
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
let mut config_snapshot = Snapshot::new(self.id().as_str());
config_snapshot.add_data_section(SnapshotDataSection {
id: format!("{}-section", self.id()),
snapshot,
});
Ok(config_snapshot)
}
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
if let Some(config_section) = snapshot
.snapshot_data
.get(&format!("{}-section", self.id()))
{
let config_state = match serde_json::from_slice(&config_section.snapshot) {
Ok(state) => state,
Err(error) => {
return Err(MigratableError::Restore(anyhow!(
"Could not deserialize {}: {}",
self.id(),
error
)))
}
};
self.set_state(&config_state);
return Ok(());
}
Err(MigratableError::Restore(anyhow!(
"Could not find {} snapshot section",
self.id()
)))
}
}
impl Default for PciBarConfiguration { impl Default for PciBarConfiguration {
fn default() -> Self { fn default() -> Self {
PciBarConfiguration { PciBarConfiguration {
@@ -862,6 +1020,7 @@ mod tests {
let mut cfg = PciConfiguration::new( let mut cfg = PciConfiguration::new(
0x1234, 0x1234,
0x5678, 0x5678,
0x1,
PciClassCode::MultimediaController, PciClassCode::MultimediaController,
&PciMultimediaSubclass::AudioController, &PciMultimediaSubclass::AudioController,
None, None,
@@ -917,6 +1076,7 @@ mod tests {
let cfg = PciConfiguration::new( let cfg = PciConfiguration::new(
0x1234, 0x1234,
0x5678, 0x5678,
0x1,
PciClassCode::MultimediaController, PciClassCode::MultimediaController,
&PciMultimediaSubclass::AudioController, &PciMultimediaSubclass::AudioController,
Some(&TestPI::Test), Some(&TestPI::Test),

0
pci/src/device.rs Executable file → Normal file
View File

View File

@@ -6,6 +6,10 @@
#[macro_use] #[macro_use]
extern crate log; extern crate log;
extern crate devices; extern crate devices;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate vm_memory; extern crate vm_memory;
mod bus; mod bus;
@@ -13,6 +17,7 @@ mod configuration;
mod device; mod device;
mod msi; mod msi;
mod msix; mod msix;
mod vfio;
pub use self::bus::{PciBus, PciConfigIo, PciConfigMmio, PciRoot, PciRootError}; pub use self::bus::{PciBus, PciConfigIo, PciConfigMmio, PciRoot, PciRootError};
pub use self::configuration::{ pub use self::configuration::{
@@ -25,6 +30,7 @@ pub use self::device::{
}; };
pub use self::msi::{msi_num_enabled_vectors, MsiCap, MsiConfig}; pub use self::msi::{msi_num_enabled_vectors, MsiCap, MsiConfig};
pub use self::msix::{MsixCap, MsixConfig, MsixTableEntry, MSIX_TABLE_ENTRY_SIZE}; pub use self::msix::{MsixCap, MsixConfig, MsixTableEntry, MSIX_TABLE_ENTRY_SIZE};
pub use self::vfio::{VfioPciDevice, VfioPciError};
/// PCI has four interrupt pins A->D. /// PCI has four interrupt pins A->D.
#[derive(Copy, Clone)] #[derive(Copy, Clone)]

View File

@@ -6,14 +6,17 @@
extern crate byteorder; extern crate byteorder;
extern crate vm_memory; extern crate vm_memory;
use std::sync::Arc;
use crate::{PciCapability, PciCapabilityID}; use crate::{PciCapability, PciCapabilityID};
use anyhow::anyhow;
use byteorder::{ByteOrder, LittleEndian}; use byteorder::{ByteOrder, LittleEndian};
use std::io;
use std::result;
use std::sync::Arc;
use vm_device::interrupt::{ use vm_device::interrupt::{
InterruptIndex, InterruptSourceConfig, InterruptSourceGroup, MsiIrqSourceConfig, InterruptIndex, InterruptSourceConfig, InterruptSourceGroup, MsiIrqSourceConfig,
}; };
use vm_memory::ByteValued; use vm_memory::ByteValued;
use vm_migration::{MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable};
const MAX_MSIX_VECTORS_PER_DEVICE: u16 = 2048; const MAX_MSIX_VECTORS_PER_DEVICE: u16 = 2048;
const MSIX_TABLE_ENTRIES_MODULO: u64 = 16; const MSIX_TABLE_ENTRIES_MODULO: u64 = 16;
@@ -25,7 +28,15 @@ const FUNCTION_MASK_MASK: u16 = (1 << FUNCTION_MASK_BIT) as u16;
const MSIX_ENABLE_MASK: u16 = (1 << MSIX_ENABLE_BIT) as u16; const MSIX_ENABLE_MASK: u16 = (1 << MSIX_ENABLE_BIT) as u16;
pub const MSIX_TABLE_ENTRY_SIZE: usize = 16; pub const MSIX_TABLE_ENTRY_SIZE: usize = 16;
#[derive(Debug, Clone)] #[derive(Debug)]
enum Error {
/// Failed enabling the interrupt route.
EnableInterruptRoute(io::Error),
/// Failed updating the interrupt route.
UpdateInterruptRoute(io::Error),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MsixTableEntry { pub struct MsixTableEntry {
pub msg_addr_lo: u32, pub msg_addr_lo: u32,
pub msg_addr_hi: u32, pub msg_addr_hi: u32,
@@ -50,6 +61,14 @@ impl Default for MsixTableEntry {
} }
} }
#[derive(Serialize, Deserialize)]
struct MsixConfigState {
table_entries: Vec<MsixTableEntry>,
pba_entries: Vec<u64>,
masked: bool,
enabled: bool,
}
pub struct MsixConfig { pub struct MsixConfig {
pub table_entries: Vec<MsixTableEntry>, pub table_entries: Vec<MsixTableEntry>,
pub pba_entries: Vec<u64>, pub pba_entries: Vec<u64>,
@@ -80,6 +99,46 @@ impl MsixConfig {
} }
} }
fn state(&self) -> MsixConfigState {
MsixConfigState {
table_entries: self.table_entries.clone(),
pba_entries: self.pba_entries.clone(),
masked: self.masked,
enabled: self.enabled,
}
}
fn set_state(&mut self, state: &MsixConfigState) -> result::Result<(), Error> {
self.table_entries = state.table_entries.clone();
self.pba_entries = state.pba_entries.clone();
self.masked = state.masked;
self.enabled = state.enabled;
if self.enabled && !self.masked {
for (idx, table_entry) in self.table_entries.iter().enumerate() {
if table_entry.masked() {
continue;
}
let config = MsiIrqSourceConfig {
high_addr: table_entry.msg_addr_hi,
low_addr: table_entry.msg_addr_lo,
data: table_entry.msg_data,
};
self.interrupt_source_group
.update(idx as InterruptIndex, InterruptSourceConfig::MsiIrq(config))
.map_err(Error::UpdateInterruptRoute)?;
self.interrupt_source_group
.enable()
.map_err(Error::EnableInterruptRoute)?;
}
}
Ok(())
}
pub fn masked(&self) -> bool { pub fn masked(&self) -> bool {
self.masked self.masked
} }
@@ -365,6 +424,52 @@ impl MsixConfig {
} }
} }
impl Pausable for MsixConfig {}
impl Snapshottable for MsixConfig {
fn id(&self) -> String {
String::from("msix_config")
}
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
let snapshot =
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
let mut msix_snapshot = Snapshot::new(self.id().as_str());
msix_snapshot.add_data_section(SnapshotDataSection {
id: format!("{}-section", self.id()),
snapshot,
});
Ok(msix_snapshot)
}
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
if let Some(msix_section) = snapshot
.snapshot_data
.get(&format!("{}-section", self.id()))
{
let msix_state = match serde_json::from_slice(&msix_section.snapshot) {
Ok(state) => state,
Err(error) => {
return Err(MigratableError::Restore(anyhow!(
"Could not deserialize MSI-X {}",
error
)))
}
};
return self.set_state(&msix_state).map_err(|e| {
MigratableError::Restore(anyhow!("Could not restore MSI-X state {:?}", e))
});
}
Err(MigratableError::Restore(anyhow!(
"Could not find MSI-X snapshot section"
)))
}
}
#[allow(dead_code)] #[allow(dead_code)]
#[repr(packed)] #[repr(packed)]
#[derive(Clone, Copy, Default)] #[derive(Clone, Copy, Default)]

View File

@@ -4,30 +4,33 @@
// //
extern crate devices; extern crate devices;
extern crate pci;
extern crate vm_allocator; extern crate vm_allocator;
use crate::vfio_device::VfioDevice; use crate::{
use byteorder::{ByteOrder, LittleEndian};
use devices::BusDevice;
use kvm_bindings::kvm_userspace_memory_region;
use kvm_ioctls::*;
use pci::{
msi_num_enabled_vectors, BarReprogrammingParams, MsiConfig, MsixCap, MsixConfig, msi_num_enabled_vectors, BarReprogrammingParams, MsiConfig, MsixCap, MsixConfig,
PciBarConfiguration, PciBarRegionType, PciCapabilityID, PciClassCode, PciConfiguration, PciBarConfiguration, PciBarRegionType, PciCapabilityID, PciClassCode, PciConfiguration,
PciDevice, PciDeviceError, PciHeaderType, PciSubclass, MSIX_TABLE_ENTRY_SIZE, PciDevice, PciDeviceError, PciHeaderType, PciSubclass, MSIX_TABLE_ENTRY_SIZE,
}; };
use byteorder::{ByteOrder, LittleEndian};
use devices::BusDevice;
use kvm_bindings::kvm_userspace_memory_region;
use kvm_ioctls::*;
use std::any::Any; use std::any::Any;
use std::ops::Deref;
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::ptr::null_mut; use std::ptr::null_mut;
use std::sync::Arc; use std::sync::Arc;
use std::{fmt, io, result}; use std::{fmt, io, result};
use vfio_bindings::bindings::vfio::*; use vfio_bindings::bindings::vfio::*;
use vfio_ioctls::{VfioDevice, VfioError};
use vm_allocator::SystemAllocator; use vm_allocator::SystemAllocator;
use vm_device::interrupt::{ use vm_device::interrupt::{
InterruptIndex, InterruptManager, InterruptSourceGroup, MsiIrqGroupConfig, InterruptIndex, InterruptManager, InterruptSourceGroup, MsiIrqGroupConfig,
}; };
use vm_memory::{Address, GuestAddress, GuestUsize}; use vm_memory::{
Address, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap, GuestRegionMmap,
GuestUsize,
};
use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::eventfd::EventFd;
#[derive(Debug)] #[derive(Debug)]
@@ -41,6 +44,7 @@ pub enum VfioPciError {
SetGsiRouting(kvm_ioctls::Error), SetGsiRouting(kvm_ioctls::Error),
MsiNotConfigured, MsiNotConfigured,
MsixNotConfigured, MsixNotConfigured,
UpdateMemory(VfioError),
UpdateMsiEventFd, UpdateMsiEventFd,
UpdateMsixEventFd, UpdateMsixEventFd,
} }
@@ -62,6 +66,7 @@ impl fmt::Display for VfioPciError {
VfioPciError::SetGsiRouting(e) => write!(f, "failed to set GSI routes for KVM: {}", e), VfioPciError::SetGsiRouting(e) => write!(f, "failed to set GSI routes for KVM: {}", e),
VfioPciError::MsiNotConfigured => write!(f, "MSI interrupt not yet configured"), VfioPciError::MsiNotConfigured => write!(f, "MSI interrupt not yet configured"),
VfioPciError::MsixNotConfigured => write!(f, "MSI-X interrupt not yet configured"), VfioPciError::MsixNotConfigured => write!(f, "MSI-X interrupt not yet configured"),
VfioPciError::UpdateMemory(e) => write!(f, "failed to update memory: {}", e),
VfioPciError::UpdateMsiEventFd => write!(f, "failed to update MSI eventfd"), VfioPciError::UpdateMsiEventFd => write!(f, "failed to update MSI eventfd"),
VfioPciError::UpdateMsixEventFd => write!(f, "failed to update MSI-X eventfd"), VfioPciError::UpdateMsixEventFd => write!(f, "failed to update MSI-X eventfd"),
} }
@@ -282,6 +287,7 @@ pub struct VfioPciDevice {
configuration: PciConfiguration, configuration: PciConfiguration,
mmio_regions: Vec<MmioRegion>, mmio_regions: Vec<MmioRegion>,
interrupt: Interrupt, interrupt: Interrupt,
mem: GuestMemoryAtomic<GuestMemoryMmap>,
} }
impl VfioPciDevice { impl VfioPciDevice {
@@ -290,11 +296,13 @@ impl VfioPciDevice {
vm_fd: &Arc<VmFd>, vm_fd: &Arc<VmFd>,
device: VfioDevice, device: VfioDevice,
interrupt_manager: &Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>, interrupt_manager: &Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
mem: GuestMemoryAtomic<GuestMemoryMmap>,
) -> Result<Self> { ) -> Result<Self> {
let device = Arc::new(device); let device = Arc::new(device);
device.reset(); device.reset();
let configuration = PciConfiguration::new( let configuration = PciConfiguration::new(
0,
0, 0,
0, 0,
PciClassCode::Other, PciClassCode::Other,
@@ -318,6 +326,7 @@ impl VfioPciDevice {
msi: None, msi: None,
msix: None, msix: None,
}, },
mem,
}; };
vfio_pci_device.parse_capabilities(interrupt_manager); vfio_pci_device.parse_capabilities(interrupt_manager);
@@ -610,6 +619,12 @@ impl VfioPciDevice {
} }
} }
} }
pub fn update_memory(&self, new_region: &Arc<GuestRegionMmap>) -> Result<()> {
self.device
.extend_dma_map(new_region)
.map_err(VfioPciError::UpdateMemory)
}
} }
impl Drop for VfioPciDevice { impl Drop for VfioPciDevice {
@@ -628,7 +643,11 @@ impl Drop for VfioPciDevice {
} }
} }
if self.device.unset_dma_map().is_err() { if self
.device
.setup_dma_map(self.mem.memory().deref())
.is_err()
{
error!("failed to remove all guest memory regions from iommu table"); error!("failed to remove all guest memory regions from iommu table");
} }
} }
@@ -834,7 +853,11 @@ impl PciDevice for VfioPciDevice {
} }
} }
if self.device.setup_dma_map().is_err() { if self
.device
.setup_dma_map(self.mem.memory().deref())
.is_err()
{
error!("failed to add all guest memory regions into iommu table"); error!("failed to add all guest memory regions into iommu table");
} }

View File

@@ -10,7 +10,7 @@ path = "src/qcow.rs"
[dependencies] [dependencies]
byteorder = "1.3.4" byteorder = "1.3.4"
libc = "0.2.68" libc = "0.2.71"
log = "0.4.8" log = "0.4.8"
remain = "0.2.1" remain = "0.2.1"
vmm-sys-util = ">=0.3.1" vmm-sys-util = ">=0.3.1"

0
qcow/src/qcow.rs Executable file → Normal file
View File

View File

@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file. // found in the LICENSE-BSD-3-Clause file.
use std;
use std::fmt::{self, Display}; use std::fmt::{self, Display};
use std::io; use std::io;

View File

@@ -1,10 +1,29 @@
- [v0.8.0](#v080)
- [Experimental Snapshot and Restore Support](#experimental-snapshot-and-restore-support)
- [Experimental ARM64 Support](#experimental-arm64-support)
- [Support for Using 5-level Paging in Guests](#support-for-using-5-level-paging-in-guests)
- [Virtio Device Interrupt Suppression for Network Devices](#virtio-device-interrupt-suppression-for-network-devices)
- [`vhost_user_fs` Improvements](#vhost_user_fs-improvements)
- [Notable Bug Fixes](#notable-bug-fixes)
- [Command Line and API Changes](#command-line-and-api-changes)
- [Contributors](#contributors)
- [v0.7.0](#v070)
- [Block, Network, Persistent Memory (PMEM), VirtioFS and Vsock hotplug](#block-network-persistent-memory-pmem-virtiofs-and-vsock-hotplug)
- [Alternative `libc` Support](#alternative-libc-support)
- [Multithreaded Multi Queued `vhost-user` Backends](#multithreaded-multi-queued-vhost-user-backends)
- [Initial RamFS Support](#initial-ramfs-support)
- [Alternative Memory Hotplug: `virtio-mem`](#alternative-memory-hotplug-virtio-mem)
- [`Seccomp` Sandboxing](#seccomp-sandboxing)
- [Updated Distribution Support](#updated-distribution-support)
- [Command Line and API Changes](#command-line-and-api-changes-1)
- [Contributors](#contributors-1)
- [v0.6.0](#v060) - [v0.6.0](#v060)
- [Directly Assigned Devices Hotplug](#directly-assigned-devices-hotplug) - [Directly Assigned Devices Hotplug](#directly-assigned-devices-hotplug)
- [Shared Filesystem Improvements](#shared-filesystem-improvements) - [Shared Filesystem Improvements](#shared-filesystem-improvements)
- [Block and Networking IO Self Offloading](#block-and-networking-io-self-offloading) - [Block and Networking IO Self Offloading](#block-and-networking-io-self-offloading)
- [Command Line Interface](#command-line-interface) - [Command Line Interface](#command-line-interface)
- [PVH Boot](#pvh-boot) - [PVH Boot](#pvh-boot)
- [Contributors](#contributors) - [Contributors](#contributors-2)
- [v0.5.1](#v051) - [v0.5.1](#v051)
- [v0.5.0](#v050) - [v0.5.0](#v050)
- [Virtual Machine Dynamic Resizing](#virtual-machine-dynamic-resizing) - [Virtual Machine Dynamic Resizing](#virtual-machine-dynamic-resizing)
@@ -12,7 +31,7 @@
- [New Interrupt Management Framework](#new-interrupt-management-framework) - [New Interrupt Management Framework](#new-interrupt-management-framework)
- [Development Tools](#development-tools) - [Development Tools](#development-tools)
- [Kata Containers Integration](#kata-containers-integration) - [Kata Containers Integration](#kata-containers-integration)
- [Contributors](#contributors) - [Contributors](#contributors-3)
- [v0.4.0](#v040) - [v0.4.0](#v040)
- [Dynamic virtual CPUs addition](#dynamic-virtual-cpus-addition) - [Dynamic virtual CPUs addition](#dynamic-virtual-cpus-addition)
- [Programmatic firmware tables generation](#programmatic-firmware-tables-generation) - [Programmatic firmware tables generation](#programmatic-firmware-tables-generation)
@@ -21,7 +40,7 @@
- [Userspace IOAPIC by default](#userspace-ioapic-by-default) - [Userspace IOAPIC by default](#userspace-ioapic-by-default)
- [PCI BAR reprogramming](#pci-bar-reprogramming) - [PCI BAR reprogramming](#pci-bar-reprogramming)
- [New `cloud-hypervisor` organization](#new-cloud-hypervisor-organization) - [New `cloud-hypervisor` organization](#new-cloud-hypervisor-organization)
- [Contributors](#contributors-1) - [Contributors](#contributors-4)
- [v0.3.0](#v030) - [v0.3.0](#v030)
- [Block device offloading](#block-device-offloading) - [Block device offloading](#block-device-offloading)
- [Network device backend](#network-device-backend) - [Network device backend](#network-device-backend)
@@ -48,6 +67,195 @@
- [Unit testing](#unit-testing) - [Unit testing](#unit-testing)
- [Integration tests parallelization](#integration-tests-parallelization) - [Integration tests parallelization](#integration-tests-parallelization)
# v0.8.0
This release has been tracked through the [0.8.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/10).
Highlights for `cloud-hypervisor` version 0.8.0 include:
### Experimental Snapshot and Restore Support
This release includes the first version of the snapshot and restore feature.
This allows a VM to be paused and then subsequently snapshotted. At a later
point that snapshot may be restored into a new running VM identical to the
original VM at the point it was paused.
This feature can be used for offline migration from one VM host to another, to
allow the upgrading or rebooting of the host machine transparently to the guest
or for templating the VM. This is an experimental feature and cannot be used on
a VM using passthrough (VFIO) devices. Issues with SMP have also been observed
(#1176).
### Experimental ARM64 Support
Included in this release is experimental support for running on ARM64.
Currently only `virtio-mmio` devices and a serial port are supported. Full
details can be found in the [ARM64 documentation](docs/arm64.md).
### Support for Using 5-level Paging in Guests
If the host supports it the guest is now enabled for 5-level paging (aka LA57).
This works when booting the Linux kernel with a vmlinux, bzImage or firmware
based boot. However booting an ELF kernel built with `CONFIG_PVH=y` does not
work due to current limitations in the PVH boot process.
### Virtio Device Interrupt Suppression for Network Devices
With `virtio-net` and `vhost-user-net` devices the guest can suppress
interrupts from the VMM by using the `VIRTIO_RING_F_EVENT_IDX` feature. This
can lead to an improvement in performance by reducing the number of interrupts
the guest must service.
### `vhost_user_fs` Improvements
The implementation in Cloud Hypervisor of the VirtioFS server now supports sandboxing itself with `seccomp`.
### Notable Bug Fixes
* VMs that have not yet been booted can now be deleted (#1110).
* By creating the `tap` device ahead of creating the VM it is not required to
run the `cloud-hypervisor` binary with `CAP_NET_ADMIN` (#1273).
* Block I/O via `virtio-block` or `vhost-user-block` now correctly adheres to
the specification and synchronizes to the underlying filesystem as required
based on guest feature negotiation. This avoids potential data loss (#399,
#1216).
* When booting with a large number of vCPUs then the ACPI table would be
overwritten by the SMP `MPTABLE`. When compiled with the `acpi` feature the
`MPTABLE` will no longer be generated (#1132).
* Shutting down VMs that have been paused is now supported (#816).
* Created socket files are deleted on shutdown (#1083).
* Trying to use passthrough devices (VFIO) will be rejected on `mmio` builds
(#751).
### Command Line and API Changes
This is non exhaustive list of HTTP API and command line changes:
* All user visible socket parameters are now consistently called `socket`
rather than `sock` in some cases.
* The `ch-remote` tool now shows any error message generated by the VMM
* The `wce` parameter has been removed from `--disk` as the feature is always
offered for negotiation.
* `--net` has gained a `host_mac` option that allows the setting of the MAC
address for the `tap` device on the host.
### Contributors
Many thanks to everyone who has contributed to our 0.8.0 release including some new faces.
* Anatol Belski <ab@php.net>
* Arron Wang <arron.wang@intel.com>
* Bo Chen <chen.bo@intel.com>
* Dr. David Alan Gilbert <dgilbert@redhat.com>
* Henry Wang <Henry.Wang@arm.com>
* Hui Zhu <teawater@antfin.com>
* LiYa'nan <oliverliyn@gmail.com>
* Michael Zhao <michael.zhao@arm.com>
* Rob Bradford <robert.bradford@intel.com>
* Samuel Ortiz <sameo@linux.intel.com>
* Sebastien Boeuf <sebastien.boeuf@intel.com>
* Sergio Lopez <slp@redhat.com>
# v0.7.0
This release has been tracked through the [0.7.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/7).
Highlights for `cloud-hypervisor` version 0.7.0 include:
### Block, Network, Persistent Memory (PMEM), VirtioFS and Vsock hotplug
Further to our effort to support modifying a running guest we now support
hotplug and unplug of the following virtio backed devices: block, network,
pmem, virtio-fs and vsock. This functionality is available on the (default) PCI
based tranport and is exposed through the HTTP API. The `ch-remote` utility
provides a CLI for adding or removing these device types after the VM has
booted. User can use the `id` parameter on the devices to choose names for
devices to ease their removal.
### Alternative `libc` Support
Cloud Hypervisor can now be compiled with the `musl` C library and this release
contains a static binary compiled using that toolchain.
### Multithreaded Multi Queued `vhost-user` Backends
The `vhost-user` backends for network and block support that are shipped by
Cloud Hypervisor have been enhanced to support multiple threads and queues to
improve throughput. These backends are used automatically if `vhost_user=true`
is passed when the devices are created.
### Initial RamFS Support
By passing the `--initramfs` command line option the user can specify a file to
be loaded into the guest memory to be used as the kernel initial filesystem.
This is usually used to allow the loading of drivers needed to be able to
access the real root filesystem but it can also be used standalone for a very
minimal image.
### Alternative Memory Hotplug: `virtio-mem`
As well as supporting ACPI based hotplug Cloud Hypervisor now supports using
the `virtio-mem` hotplug alternative. This can be controlled by the
`hotplug_method` parameter on the `--memory` command line option. It currently
requires kernel patches to be able to support it.
### `Seccomp` Sandboxing
Cloud Hypervisor now has support for restricting the system calls that the
process can use via the `seccomp` security API. This on by default and is
controlled by the `--seccomp` command line option.
### Updated Distribution Support
With the release of Ubuntu 20.04 we have added that to the list of supported
distributions and is part of our regular testing programme.
### Command Line and API Changes
This is non exhaustive list of HTTP API and command line changes
* New `id` fields added for devices to allow them to be named to ease removal.
If no name is specified the VMM chooses one.
* Use `--memory`'s `shared` and `hugepages` controls for determining backing
memory instead of providing a path.
* The `--vsock` parameter only takes one device as the Linux kernel only
supports a single Vsock device. The REST API has removed the vector for this
option and replaced it with a single optional field.
* There is enhanced validation of the command line and API provided
configurations to ensure that the provided options are compatible e.g. that
shared memory is in use if any attempt is made to used a `vhost-user` backed
device.
* `ch-remote` has added `add-disk`, `add-fs`, `add-net`, `add-pmem` and
`add-vsock` subcommands. For removal `remove-device` is used. The REST API
has appropriate new HTTP endpoints too.
* Specifying a `size` with `--pmem` is no longer required and instead the size
will be obtained from the file. A `discard_writes` option has also been added
to provide the equivalent of a read-only file.
* The parameters to `--block-backend` have been changed to more closely align
with those used by `--disk`.
### Contributors
Many thanks to everyone who has contributed to our 0.7.0 release including some new faces.
* Alejandro Jimenez <alejandro.j.jimenez@oracle.com>
* Bo Chen <chen.bo@intel.com>
* Cathy Zhang <cathy.zhang@intel.com>
* Damjan Georgievski <gdamjan@gmail.com>
* Dean Sheather <dean@coder.com>
* Eryu Guan <eguan@linux.alibaba.com>
* Hui Zhu <teawater@antfin.com>
* Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
* Martin Xu <martin.xu@intel.com>
* Muminul Islam <muislam@microsoft.com>
* Rob Bradford <robert.bradford@intel.com>
* Samuel Ortiz <sameo@linux.intel.com>
* Sebastien Boeuf <sebastien.boeuf@intel.com>
* Sergio Lopez <slp@redhat.com>
* Yang Zhong <yang.zhong@intel.com>
* Yi Sun <yi.y.sun@linux.intel.com>
# v0.6.0 # v0.6.0
This release has been tracked through the [0.6.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/7). This release has been tracked through the [0.6.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/7).

View File

@@ -1,6 +1,7 @@
FROM ubuntu:18.04 as dev FROM ubuntu:18.04 as dev
ARG RUST_TOOLCHAIN="1.42.0" ARG TARGETARCH="x86_64"
ARG RUST_TOOLCHAIN="1.43.0"
ARG CLH_SRC_DIR="/cloud-hypervisor" ARG CLH_SRC_DIR="/cloud-hypervisor"
ARG CLH_BUILD_DIR="$CLH_SRC_DIR/build" ARG CLH_BUILD_DIR="$CLH_SRC_DIR/build"
ARG CARGO_REGISTRY_DIR="$CLH_BUILD_DIR/cargo_registry" ARG CARGO_REGISTRY_DIR="$CLH_BUILD_DIR/cargo_registry"
@@ -11,9 +12,9 @@ ENV RUSTUP_HOME=$CARGO_HOME
ENV PATH="$PATH:$CARGO_HOME/bin" ENV PATH="$PATH:$CARGO_HOME/bin"
# Install all CI dependencies # Install all CI dependencies
RUN apt-get update RUN apt-get update \
RUN apt-get -yq upgrade && apt-get -yq upgrade \
RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq \ && DEBIAN_FRONTEND=noninteractive apt-get install -yq \
build-essential \ build-essential \
bc \ bc \
docker.io \ docker.io \
@@ -21,6 +22,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq \
wget \ wget \
sudo \ sudo \
mtools \ mtools \
musl-tools \
libssl-dev \ libssl-dev \
pkg-config \ pkg-config \
flex \ flex \
@@ -34,13 +36,31 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq \
libcap-ng-dev \ libcap-ng-dev \
socat \ socat \
dosfstools \ dosfstools \
cpio \
bsdtar \
libfdt-dev \
&& apt-get clean \ && apt-get clean \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
RUN if [ "$TARGETARCH" = "x86_64" ]; then \
apt-get update \
&& apt-get -yq upgrade \
&& DEBIAN_FRONTEND=noninteractive apt-get install -yq gcc-multilib \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*; fi
# Fix the libssl-dev install
RUN cp /usr/include/"$TARGETARCH"-linux-gnu/openssl/opensslconf.h /usr/include/openssl/
ENV OPENSSL_DIR=/usr/lib/"$TARGETARCH"-linux-gnu/
ENV OPENSSL_LIB_DIR=/usr/lib/"$TARGETARCH"-linux-gnu/
ENV OPENSSL_INCLUDE_DIR=/usr/include/
# Install the rust toolchain # Install the rust toolchain
RUN nohup curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain "$RUST_TOOLCHAIN" \ RUN nohup curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain "$RUST_TOOLCHAIN" \
&& rustup component add rustfmt \ && rustup target add $TARGETARCH-unknown-linux-musl --toolchain "$RUST_TOOLCHAIN" \
&& rustup component add clippy \ && if [ "$TARGETARCH" = "x86_64" ]; then rustup toolchain add $RUST_TOOLCHAIN-x86_64-unknown-linux-musl; fi \
&& if [ "$TARGETARCH" = "x86_64" ]; then rustup component add rustfmt; fi \
&& if [ "$TARGETARCH" = "x86_64" ]; then rustup component add clippy; fi \
&& cargo install cargo-audit \ && cargo install cargo-audit \
&& rm -rf "$CARGO_HOME/registry" \ && rm -rf "$CARGO_HOME/registry" \
&& ln -s "$CARGO_REGISTRY_DIR" "$CARGO_HOME/registry" \ && ln -s "$CARGO_REGISTRY_DIR" "$CARGO_HOME/registry" \

View File

@@ -178,7 +178,14 @@ CONFIG_NET_NS=y
CONFIG_SCHED_AUTOGROUP=y CONFIG_SCHED_AUTOGROUP=y
# CONFIG_SYSFS_DEPRECATED is not set # CONFIG_SYSFS_DEPRECATED is not set
CONFIG_RELAY=y CONFIG_RELAY=y
# CONFIG_BLK_DEV_INITRD is not set CONFIG_BLK_DEV_INITRD=y
CONFIG_INITRAMFS_SOURCE=""
CONFIG_RD_GZIP=y
CONFIG_RD_BZIP2=y
CONFIG_RD_LZMA=y
CONFIG_RD_XZ=y
CONFIG_RD_LZO=y
CONFIG_RD_LZ4=y
# CONFIG_BOOT_CONFIG is not set # CONFIG_BOOT_CONFIG is not set
CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE=y CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE=y
# CONFIG_CC_OPTIMIZE_FOR_SIZE is not set # CONFIG_CC_OPTIMIZE_FOR_SIZE is not set
@@ -279,7 +286,7 @@ CONFIG_X86_64_SMP=y
CONFIG_ARCH_SUPPORTS_UPROBES=y CONFIG_ARCH_SUPPORTS_UPROBES=y
CONFIG_FIX_EARLYCON_MEM=y CONFIG_FIX_EARLYCON_MEM=y
CONFIG_DYNAMIC_PHYSICAL_MASK=y CONFIG_DYNAMIC_PHYSICAL_MASK=y
CONFIG_PGTABLE_LEVELS=4 CONFIG_PGTABLE_LEVELS=5
CONFIG_CC_HAS_SANE_STACKPROTECTOR=y CONFIG_CC_HAS_SANE_STACKPROTECTOR=y
# #
@@ -364,7 +371,7 @@ CONFIG_X86_VSYSCALL_EMULATION=y
# CONFIG_MICROCODE is not set # CONFIG_MICROCODE is not set
CONFIG_X86_MSR=y CONFIG_X86_MSR=y
CONFIG_X86_CPUID=y CONFIG_X86_CPUID=y
# CONFIG_X86_5LEVEL is not set CONFIG_X86_5LEVEL=y
CONFIG_X86_DIRECT_GBPAGES=y CONFIG_X86_DIRECT_GBPAGES=y
# CONFIG_X86_CPA_STATISTICS is not set # CONFIG_X86_CPA_STATISTICS is not set
CONFIG_AMD_MEM_ENCRYPT=y CONFIG_AMD_MEM_ENCRYPT=y
@@ -485,6 +492,7 @@ CONFIG_ACPI_HOTPLUG_CPU=y
# CONFIG_ACPI_PROCESSOR_AGGREGATOR is not set # CONFIG_ACPI_PROCESSOR_AGGREGATOR is not set
CONFIG_ACPI_THERMAL=y CONFIG_ACPI_THERMAL=y
CONFIG_ARCH_HAS_ACPI_TABLE_UPGRADE=y CONFIG_ARCH_HAS_ACPI_TABLE_UPGRADE=y
CONFIG_ACPI_TABLE_UPGRADE=y
# CONFIG_ACPI_DEBUG is not set # CONFIG_ACPI_DEBUG is not set
# CONFIG_ACPI_PCI_SLOT is not set # CONFIG_ACPI_PCI_SLOT is not set
CONFIG_ACPI_CONTAINER=y CONFIG_ACPI_CONTAINER=y
@@ -2418,7 +2426,22 @@ CONFIG_ZLIB_INFLATE=y
CONFIG_ZLIB_DEFLATE=y CONFIG_ZLIB_DEFLATE=y
CONFIG_LZO_COMPRESS=y CONFIG_LZO_COMPRESS=y
CONFIG_LZO_DECOMPRESS=y CONFIG_LZO_DECOMPRESS=y
# CONFIG_XZ_DEC is not set CONFIG_LZ4_DECOMPRESS=y
CONFIG_XZ_DEC=y
CONFIG_XZ_DEC_X86=y
CONFIG_XZ_DEC_POWERPC=y
CONFIG_XZ_DEC_IA64=y
CONFIG_XZ_DEC_ARM=y
CONFIG_XZ_DEC_ARMTHUMB=y
CONFIG_XZ_DEC_SPARC=y
CONFIG_XZ_DEC_BCJ=y
# CONFIG_XZ_DEC_TEST is not set
CONFIG_DECOMPRESS_GZIP=y
CONFIG_DECOMPRESS_BZIP2=y
CONFIG_DECOMPRESS_LZMA=y
CONFIG_DECOMPRESS_XZ=y
CONFIG_DECOMPRESS_LZO=y
CONFIG_DECOMPRESS_LZ4=y
CONFIG_INTERVAL_TREE=y CONFIG_INTERVAL_TREE=y
CONFIG_XARRAY_MULTI=y CONFIG_XARRAY_MULTI=y
CONFIG_ASSOCIATIVE_ARRAY=y CONFIG_ASSOCIATIVE_ARRAY=y

View File

@@ -7,7 +7,7 @@
CLI_NAME="Cloud Hypervisor" CLI_NAME="Cloud Hypervisor"
CTR_IMAGE_TAG="cloudhypervisor/dev" CTR_IMAGE_TAG="cloudhypervisor/dev"
CTR_IMAGE_VERSION="v1" CTR_IMAGE_VERSION="v3"
CTR_IMAGE="${CTR_IMAGE_TAG}:${CTR_IMAGE_VERSION}" CTR_IMAGE="${CTR_IMAGE_TAG}:${CTR_IMAGE_VERSION}"
DOCKER_RUNTIME="docker" DOCKER_RUNTIME="docker"
@@ -113,6 +113,13 @@ ensure_build_dir() {
done done
} }
# Make sure we're using the latest dev container, by just pulling it.
ensure_latest_ctr() {
$DOCKER_RUNTIME pull "$CTR_IMAGE"
ok_or_die "Error pulling container image. Aborting."
}
# Fix main directory permissions after a container ran as root. # Fix main directory permissions after a container ran as root.
# Since the container ran as root, any files it creates will be owned by root. # Since the container ran as root, any files it creates will be owned by root.
# This fixes that by recursively changing the ownership of /cloud-hypervisor to the # This fixes that by recursively changing the ownership of /cloud-hypervisor to the
@@ -139,16 +146,18 @@ cmd_help() {
echo "" echo ""
echo "Available commands:" echo "Available commands:"
echo "" echo ""
echo " build [--debug|--release] [-- [<cargo args>]]" echo " build [--debug|--release] [--libc musl|gnu] [-- [<cargo args>]]"
echo " Build the Cloud Hypervisor binaries." echo " Build the Cloud Hypervisor binaries."
echo " --debug Build the debug binaries. This is the default." echo " --debug Build the debug binaries. This is the default."
echo " --release Build the release binaries." echo " --release Build the release binaries."
echo " --libc Select the C library Cloud Hypervisor will be built against. Default is gnu"
echo "" echo ""
echo " tests [--unit|--cargo|--all] [-- [<cargo test args>]]" echo " tests [--unit|--cargo|--all] [--libc musl|gnu] [-- [<cargo test args>]]"
echo " Run the Cloud Hypervisor tests." echo " Run the Cloud Hypervisor tests."
echo " --unit Run the unit tests." echo " --unit Run the unit tests."
echo " --cargo Run the cargo tests." echo " --cargo Run the cargo tests."
echo " --integration Run the integration tests." echo " --integration Run the integration tests."
echo " --libc Select the C library Cloud Hypervisor will be built against. Default is gnu"
echo " --all Run all tests." echo " --all Run all tests."
echo "" echo ""
echo " build-container [--type]" echo " build-container [--type]"
@@ -165,12 +174,19 @@ cmd_help() {
cmd_build() { cmd_build() {
build="debug" build="debug"
libc="gnu"
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
case "$1" in case "$1" in
"-h"|"--help") { cmd_help; exit 1; } ;; "-h"|"--help") { cmd_help; exit 1; } ;;
"--debug") { build="debug"; } ;; "--debug") { build="debug"; } ;;
"--release") { build="release"; } ;; "--release") { build="release"; } ;;
"--libc")
shift
[[ "$1" =~ ^(musl|gnu)$ ]] || \
die "Invalid libc: $1. Valid options are \"musl\" and \"gnu\"."
libc="$1"
;;
"--") { shift; break; } ;; "--") { shift; break; } ;;
*) *)
die "Unknown build argument: $1. Please use --help for help." die "Unknown build argument: $1. Please use --help for help."
@@ -179,8 +195,23 @@ cmd_build() {
shift shift
done done
target="$(uname -m)-unknown-linux-${libc}"
cargo_args=("$@") cargo_args=("$@")
[ $build = "release" ] && cargo_args+=("--release") [ $build = "release" ] && cargo_args+=("--release")
cargo_args+=(--target "$target")
[ $(uname -m) = "aarch64" ] && cargo_args+=("--no-default-features")
[ $(uname -m) = "aarch64" ] && cargo_args+=(--features "mmio")
rustflags=""
if [ $(uname -m) = "aarch64" ] && [ $libc = "musl" ] ; then
rustflags="-C link-arg=-lgcc"
fi
# A workaround on Arm64 to avoid build errors in kvm-bindings
if [ $(uname -m) = "aarch64" ]; then
sed -i 's/"with-serde",\ //g' "$CLH_ROOT_DIR"/vmm/Cargo.toml
fi
$DOCKER_RUNTIME run \ $DOCKER_RUNTIME run \
--user "$(id -u):$(id -g)" \ --user "$(id -u):$(id -g)" \
@@ -188,10 +219,11 @@ cmd_build() {
--rm \ --rm \
--volume /dev:/dev \ --volume /dev:/dev \
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
--env RUSTFLAGS="$rustflags" \
"$CTR_IMAGE" \ "$CTR_IMAGE" \
cargo build \ cargo build \
--target-dir "$CTR_CLH_CARGO_TARGET" \ --target-dir "$CTR_CLH_CARGO_TARGET" \
"${cargo_args[@]}" && say "Binaries placed under $CLH_CARGO_TARGET/$build" "${cargo_args[@]}" && say "Binaries placed under $CLH_CARGO_TARGET/$target/$build"
} }
cmd_clean() { cmd_clean() {
@@ -212,15 +244,22 @@ cmd_tests() {
unit=false unit=false
cargo=false cargo=false
integration=false integration=false
libc="gnu"
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
case "$1" in case "$1" in
"-h"|"--help") { cmd_help; exit 1; } ;; "-h"|"--help") { cmd_help; exit 1; } ;;
"--unit") { unit=true; } ;; "--unit") { unit=true; } ;;
"--cargo") { cargo=true; } ;; "--cargo") { cargo=true; } ;;
"--integration") { integration=true; } ;; "--integration") { integration=true; } ;;
"--all") { cargo=true; unit=true; integration=true; } ;; "--libc")
"--") { shift; break; } ;; shift
[[ "$1" =~ ^(musl|gnu)$ ]] || \
die "Invalid libc: $1. Valid options are \"musl\" and \"gnu\"."
libc="$1"
;;
"--all") { cargo=true; unit=true; integration=true; } ;;
"--") { shift; break; } ;;
*) *)
die "Unknown tests argument: $1. Please use --help for help." die "Unknown tests argument: $1. Please use --help for help."
;; ;;
@@ -228,8 +267,16 @@ cmd_tests() {
shift shift
done done
target="$(uname -m)-unknown-linux-${libc}"
cflags=""
target_cc=""
if [[ "$target" == "x86_64-unknown-linux-musl" ]]; then
target_cc="musl-gcc"
cflags="-I /usr/include/x86_64-linux-musl/ -idirafter /usr/include/"
fi
if [ "$unit" = true ] ; then if [ "$unit" = true ] ; then
say "Running unit tests..." say "Running unit tests for $target..."
$DOCKER_RUNTIME run \ $DOCKER_RUNTIME run \
--workdir "$CTR_CLH_ROOT_DIR" \ --workdir "$CTR_CLH_ROOT_DIR" \
--rm \ --rm \
@@ -237,6 +284,9 @@ cmd_tests() {
--device /dev/net/tun \ --device /dev/net/tun \
--cap-add net_admin \ --cap-add net_admin \
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
--env BUILD_TARGET="$target" \
--env CFLAGS="$cflags" \
--env TARGET_CC="$target_cc" \
"$CTR_IMAGE" \ "$CTR_IMAGE" \
./scripts/run_unit_tests.sh "$@" || fix_dir_perms $? || exit $? ./scripts/run_unit_tests.sh "$@" || fix_dir_perms $? || exit $?
fi fi
@@ -252,7 +302,7 @@ cmd_tests() {
fi fi
if [ "$integration" = true ] ; then if [ "$integration" = true ] ; then
say "Running integration tests..." say "Running integration tests for $target..."
$DOCKER_RUNTIME run \ $DOCKER_RUNTIME run \
--workdir "$CTR_CLH_ROOT_DIR" \ --workdir "$CTR_CLH_ROOT_DIR" \
--rm \ --rm \
@@ -265,6 +315,7 @@ cmd_tests() {
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
--env USER="root" \ --env USER="root" \
--env CH_LIBC="${libc}" \
"$CTR_IMAGE" \ "$CTR_IMAGE" \
./scripts/run_integration_tests.sh "$@" || fix_dir_perms $? || exit $? ./scripts/run_integration_tests.sh "$@" || fix_dir_perms $? || exit $?
fi fi
@@ -296,6 +347,7 @@ cmd_build-container() {
--target $container_type \ --target $container_type \
-t $CTR_IMAGE \ -t $CTR_IMAGE \
-f $BUILD_DIR/Dockerfile \ -f $BUILD_DIR/Dockerfile \
--build-arg TARGETARCH="$(uname -m)" \
$BUILD_DIR $BUILD_DIR
} }
@@ -325,5 +377,13 @@ cmd=cmd_$1
shift shift
ensure_build_dir ensure_build_dir
if [ $(uname -m) = "x86_64" ]; then
ensure_latest_ctr
fi
# Before a public image for AArch64 ready, we build the container if needed.
if [ $(uname -m) = "aarch64" ]; then
cmd_build-container
fi
$cmd "$@" $cmd "$@"

View File

@@ -3,6 +3,8 @@ set -x
source $HOME/.cargo/env source $HOME/.cargo/env
export BUILD_TARGET=${BUILD_TARGET-x86_64-unknown-linux-gnu}
WORKLOADS_DIR="$HOME/workloads" WORKLOADS_DIR="$HOME/workloads"
mkdir -p "$WORKLOADS_DIR" mkdir -p "$WORKLOADS_DIR"
@@ -51,20 +53,46 @@ if [ ! -f "$BIONIC_OS_RAW_IMAGE" ]; then
fi fi
EOAN_OS_IMAGE_NAME="eoan-server-cloudimg-amd64.img" FOCAL_OS_IMAGE_NAME="focal-server-cloudimg-amd64.img"
EOAN_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$EOAN_OS_IMAGE_NAME" FOCAL_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$FOCAL_OS_IMAGE_NAME"
EOAN_OS_IMAGE="$WORKLOADS_DIR/$EOAN_OS_IMAGE_NAME" FOCAL_OS_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_IMAGE_NAME"
if [ ! -f "$EOAN_OS_IMAGE" ]; then if [ ! -f "$FOCAL_OS_IMAGE" ]; then
pushd $WORKLOADS_DIR pushd $WORKLOADS_DIR
time wget --quiet $EOAN_OS_IMAGE_URL || exit 1 time wget --quiet $FOCAL_OS_IMAGE_URL || exit 1
popd popd
fi fi
EOAN_OS_RAW_IMAGE_NAME="eoan-server-cloudimg-amd64-raw.img" FOCAL_OS_RAW_IMAGE_NAME="focal-server-cloudimg-amd64-raw.img"
EOAN_OS_RAW_IMAGE="$WORKLOADS_DIR/$EOAN_OS_RAW_IMAGE_NAME" FOCAL_OS_RAW_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_RAW_IMAGE_NAME"
if [ ! -f "$EOAN_OS_RAW_IMAGE" ]; then if [ ! -f "$FOCAL_OS_RAW_IMAGE" ]; then
pushd $WORKLOADS_DIR pushd $WORKLOADS_DIR
time qemu-img convert -p -f qcow2 -O raw $EOAN_OS_IMAGE_NAME $EOAN_OS_RAW_IMAGE_NAME || exit 1 time qemu-img convert -p -f qcow2 -O raw $FOCAL_OS_IMAGE_NAME $FOCAL_OS_RAW_IMAGE_NAME || exit 1
popd
fi
ALPINE_MINIROOTFS_URL="http://dl-cdn.alpinelinux.org/alpine/v3.11/releases/x86_64/alpine-minirootfs-3.11.3-x86_64.tar.gz"
ALPINE_MINIROOTFS_TARBALL="$WORKLOADS_DIR/alpine-minirootfs-x86_64.tar.gz"
if [ ! -f "$ALPINE_MINIROOTFS_TARBALL" ]; then
pushd $WORKLOADS_DIR
time wget --quiet $ALPINE_MINIROOTFS_URL -O $ALPINE_MINIROOTFS_TARBALL || exit 1
popd
fi
ALPINE_INITRAMFS_IMAGE="$WORKLOADS_DIR/alpine_initramfs.img"
if [ ! -f "$ALPINE_INITRAMFS_IMAGE" ]; then
pushd $WORKLOADS_DIR
mkdir alpine-minirootfs
tar xf "$ALPINE_MINIROOTFS_TARBALL" -C alpine-minirootfs
cat > alpine-minirootfs/init <<-EOF
#! /bin/sh
mount -t devtmpfs dev /dev
echo \$TEST_STRING > /dev/console
poweroff -f
EOF
chmod +x alpine-minirootfs/init
cd alpine-minirootfs
find . -print0 |
cpio --null --create --verbose --owner root:root --format=newc > "$ALPINE_INITRAMFS_IMAGE"
popd popd
fi fi
@@ -182,13 +210,28 @@ sudo ip tuntap add name vunet-tap0 mode tap
# Create tap interface with multipe queues support for vhost_user_net test. # Create tap interface with multipe queues support for vhost_user_net test.
sudo ip tuntap add name vunet-tap1 mode tap multi_queue sudo ip tuntap add name vunet-tap1 mode tap multi_queue
cargo build --release BUILD_TARGET="$(uname -m)-unknown-linux-${CH_LIBC}"
sudo setcap cap_net_admin+ep target/release/cloud-hypervisor CFLAGS=""
sudo setcap cap_net_admin+ep target/release/vhost_user_net TARGET_CC=""
if [[ "${BUILD_TARGET}" == "x86_64-unknown-linux-musl" ]]; then
TARGET_CC="musl-gcc"
CFLAGS="-I /usr/include/x86_64-linux-musl/ -idirafter /usr/include/"
fi
cargo build --release --target $BUILD_TARGET
strip target/$BUILD_TARGET/release/cloud-hypervisor
strip target/$BUILD_TARGET/release/vhost_user_net
strip target/$BUILD_TARGET/release/ch-remote
# Copy for non-privileged net test
cp target/$BUILD_TARGET/release/cloud-hypervisor target/$BUILD_TARGET/release/cloud-hypervisor-unprivileged
sudo setcap cap_net_admin+ep target/$BUILD_TARGET/release/cloud-hypervisor
sudo setcap cap_net_admin+ep target/$BUILD_TARGET/release/vhost_user_net
# We always copy a fresh version of our binary for our L2 guest. # We always copy a fresh version of our binary for our L2 guest.
cp target/release/cloud-hypervisor $VFIO_DIR cp target/$BUILD_TARGET/release/cloud-hypervisor $VFIO_DIR
cp target/release/ch-remote $VFIO_DIR cp target/$BUILD_TARGET/release/ch-remote $VFIO_DIR
# Enable KSM with some reasonable parameters so that it won't take too long # Enable KSM with some reasonable parameters so that it won't take too long
# for the memory to be merged between two processes. # for the memory to be merged between two processes.
@@ -213,8 +256,16 @@ RES=$?
if [ $RES -eq 0 ]; then if [ $RES -eq 0 ]; then
# virtio-mmio based testing # virtio-mmio based testing
cargo build --release --no-default-features --features "mmio" cargo build --release --target $BUILD_TARGET --no-default-features --features "mmio"
sudo setcap cap_net_admin+ep target/release/cloud-hypervisor strip target/$BUILD_TARGET/release/cloud-hypervisor
strip target/$BUILD_TARGET/release/vhost_user_net
strip target/$BUILD_TARGET/release/ch-remote
sudo setcap cap_net_admin+ep target/$BUILD_TARGET/release/cloud-hypervisor
# Ensure test binary has the same caps as the cloud-hypervisor one
time cargo test --no-run --features "integration_tests,mmio" -- --nocapture || exit 1
ls target/debug/deps/cloud_hypervisor-* | xargs -n 1 sudo setcap cap_net_admin+ep
newgrp kvm << EOF newgrp kvm << EOF
export RUST_BACKTRACE=1 export RUST_BACKTRACE=1

View File

@@ -2,13 +2,15 @@
source $HOME/.cargo/env source $HOME/.cargo/env
cargo test --workspace --no-run BUILD_TARGET=${BUILD_TARGET-x86_64-unknown-linux-gnu}
pushd target/debug
cargo test --target $BUILD_TARGET --workspace --no-run
pushd target/$BUILD_TARGET/debug
ls | grep net_util | grep -v "\.d" | xargs -n 1 sudo setcap cap_net_admin,cap_net_raw+ep ls | grep net_util | grep -v "\.d" | xargs -n 1 sudo setcap cap_net_admin,cap_net_raw+ep
popd popd
sudo adduser $USER kvm sudo adduser $USER kvm
newgrp kvm << EOF || exit 1 newgrp kvm << EOF || exit 1
export RUST_BACKTRACE=1 export RUST_BACKTRACE=1
cargo test --workspace "$@" || exit 1; cargo test --target $BUILD_TARGET --workspace "$@" || exit 1;
EOF EOF

View File

@@ -2,5 +2,6 @@ cf7cfa783082fc4d6b4d1c0a53e4402648c14b82 clear-31311-cloudguest.img
142a410546b592ff9536b46bb410faf8ac11edee clear-31311-cloudguest-raw.img 142a410546b592ff9536b46bb410faf8ac11edee clear-31311-cloudguest-raw.img
27f3b17962ace69b51f0ddc2012095e3109e6ed8 bionic-server-cloudimg-amd64.img 27f3b17962ace69b51f0ddc2012095e3109e6ed8 bionic-server-cloudimg-amd64.img
8db9cc58b01452ce2d06c313177e6e74d8582d93 bionic-server-cloudimg-amd64-raw.img 8db9cc58b01452ce2d06c313177e6e74d8582d93 bionic-server-cloudimg-amd64-raw.img
4a452cdcf781f95d31a1668ecb92a937c176709a eoan-server-cloudimg-amd64.img d4a44acc6014d5f83dea1c625c43d677a95fa75f alpine-minirootfs-x86_64.tar.gz
91fdfb21df8920fd55915edf7669282cda2505f6 eoan-server-cloudimg-amd64-raw.img 3cab64475048383e70a38550734e98961cfe0fd6 focal-server-cloudimg-amd64-raw.img
c74a95665b189f93cb16a2c1770820c6a6a43fb0 focal-server-cloudimg-amd64.img

View File

@@ -9,6 +9,7 @@ extern crate serde_json;
extern crate vmm; extern crate vmm;
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
use std::fmt;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::os::unix::net::UnixStream; use std::os::unix::net::UnixStream;
use std::process; use std::process;
@@ -19,10 +20,44 @@ enum Error {
StatusCodeParsing(std::num::ParseIntError), StatusCodeParsing(std::num::ParseIntError),
MissingProtocol, MissingProtocol,
ContentLengthParsing(std::num::ParseIntError), ContentLengthParsing(std::num::ParseIntError),
ServerResponse(StatusCode), ServerResponse(StatusCode, Option<String>),
InvalidCPUCount(std::num::ParseIntError), InvalidCPUCount(std::num::ParseIntError),
InvalidMemorySize(std::num::ParseIntError), InvalidMemorySize(std::num::ParseIntError),
AddDeviceConfig(vmm::config::Error), AddDeviceConfig(vmm::config::Error),
AddDiskConfig(vmm::config::Error),
AddFsConfig(vmm::config::Error),
AddPmemConfig(vmm::config::Error),
AddNetConfig(vmm::config::Error),
AddVsockConfig(vmm::config::Error),
Restore(vmm::config::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Error::*;
match self {
Socket(e) => write!(f, "Error writing to HTTP socket: {}", e),
StatusCodeParsing(e) => write!(f, "Error parsing HTTP status code: {}", e),
MissingProtocol => write!(f, "HTTP output is missing protocol statement"),
ContentLengthParsing(e) => write!(f, "Error parsing HTTP Content-Length field: {}", e),
ServerResponse(s, o) => {
if let Some(o) = o {
write!(f, "Server responded with an error: {:?}: {}", s, o)
} else {
write!(f, "Server responded with an error: {:?}", s)
}
}
InvalidCPUCount(e) => write!(f, "Error parsing CPU count: {}", e),
InvalidMemorySize(e) => write!(f, "Error parsing memory size: {}", e),
AddDeviceConfig(e) => write!(f, "Error parsing device syntax: {}", e),
AddDiskConfig(e) => write!(f, "Error parsing disk syntax: {}", e),
AddFsConfig(e) => write!(f, "Error parsing filesystem syntax: {}", e),
AddPmemConfig(e) => write!(f, "Error parsing persistent memory syntax: {}", e),
AddNetConfig(e) => write!(f, "Error parsing network syntax: {}", e),
AddVsockConfig(e) => write!(f, "Error parsing vsock syntax: {}", e),
Restore(e) => write!(f, "Error parsing restore syntax: {}", e),
}
}
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@@ -57,10 +92,10 @@ impl StatusCode {
)) ))
} }
fn check(self) -> Result<(), Error> { fn is_server_error(self) -> bool {
match self { match self {
StatusCode::OK | StatusCode::Continue | StatusCode::NoContent => Ok(()), StatusCode::OK | StatusCode::Continue | StatusCode::NoContent => false,
_ => Err(Error::ServerResponse(self)), _ => true,
} }
} }
} }
@@ -117,10 +152,14 @@ fn parse_http_response(socket: &mut UnixStream) -> Result<Option<String>, Error>
} }
} }
} }
let body_string = content_length.and(Some(String::from(&res[body_offset.unwrap()..])));
let status_code = get_status_code(&res)?;
get_status_code(&res)?.check()?; if status_code.is_server_error() {
Err(Error::ServerResponse(status_code, body_string))
Ok(content_length.and(Some(String::from(&res[body_offset.unwrap()..])))) } else {
Ok(body_string)
}
} }
fn simple_api_command( fn simple_api_command(
@@ -213,6 +252,85 @@ fn remove_device_api_command(socket: &mut UnixStream, id: &str) -> Result<(), Er
) )
} }
fn add_disk_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> {
let disk_config = vmm::config::DiskConfig::parse(config).map_err(Error::AddDiskConfig)?;
simple_api_command(
socket,
"PUT",
"add-disk",
Some(&serde_json::to_string(&disk_config).unwrap()),
)
}
fn add_fs_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> {
let fs_config = vmm::config::FsConfig::parse(config).map_err(Error::AddFsConfig)?;
simple_api_command(
socket,
"PUT",
"add-fs",
Some(&serde_json::to_string(&fs_config).unwrap()),
)
}
fn add_pmem_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> {
let pmem_config = vmm::config::PmemConfig::parse(config).map_err(Error::AddPmemConfig)?;
simple_api_command(
socket,
"PUT",
"add-pmem",
Some(&serde_json::to_string(&pmem_config).unwrap()),
)
}
fn add_net_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> {
let net_config = vmm::config::NetConfig::parse(config).map_err(Error::AddNetConfig)?;
simple_api_command(
socket,
"PUT",
"add-net",
Some(&serde_json::to_string(&net_config).unwrap()),
)
}
fn add_vsock_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> {
let vsock_config = vmm::config::VsockConfig::parse(config).map_err(Error::AddVsockConfig)?;
simple_api_command(
socket,
"PUT",
"add-vsock",
Some(&serde_json::to_string(&vsock_config).unwrap()),
)
}
fn snapshot_api_command(socket: &mut UnixStream, url: &str) -> Result<(), Error> {
let snapshot_config = vmm::api::VmSnapshotConfig {
destination_url: String::from(url),
};
simple_api_command(
socket,
"PUT",
"snapshot",
Some(&serde_json::to_string(&snapshot_config).unwrap()),
)
}
fn restore_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> {
let restore_config = vmm::config::RestoreConfig::parse(config).map_err(Error::Restore)?;
simple_api_command(
socket,
"PUT",
"restore",
Some(&serde_json::to_string(&restore_config).unwrap()),
)
}
fn do_command(matches: &ArgMatches) -> Result<(), Error> { fn do_command(matches: &ArgMatches) -> Result<(), Error> {
let mut socket = let mut socket =
UnixStream::connect(matches.value_of("api-socket").unwrap()).map_err(Error::Socket)?; UnixStream::connect(matches.value_of("api-socket").unwrap()).map_err(Error::Socket)?;
@@ -246,6 +364,62 @@ fn do_command(matches: &ArgMatches) -> Result<(), Error> {
.value_of("id") .value_of("id")
.unwrap(), .unwrap(),
), ),
Some("add-disk") => add_disk_api_command(
&mut socket,
matches
.subcommand_matches("add-disk")
.unwrap()
.value_of("disk_config")
.unwrap(),
),
Some("add-fs") => add_fs_api_command(
&mut socket,
matches
.subcommand_matches("add-fs")
.unwrap()
.value_of("fs_config")
.unwrap(),
),
Some("add-pmem") => add_pmem_api_command(
&mut socket,
matches
.subcommand_matches("add-pmem")
.unwrap()
.value_of("pmem_config")
.unwrap(),
),
Some("add-net") => add_net_api_command(
&mut socket,
matches
.subcommand_matches("add-net")
.unwrap()
.value_of("net_config")
.unwrap(),
),
Some("add-vsock") => add_vsock_api_command(
&mut socket,
matches
.subcommand_matches("add-vsock")
.unwrap()
.value_of("vsock_config")
.unwrap(),
),
Some("snapshot") => snapshot_api_command(
&mut socket,
matches
.subcommand_matches("snapshot")
.unwrap()
.value_of("snapshot_config")
.unwrap(),
),
Some("restore") => restore_api_command(
&mut socket,
matches
.subcommand_matches("restore")
.unwrap()
.value_of("restore_config")
.unwrap(),
),
Some(c) => simple_api_command(&mut socket, "PUT", c, None), Some(c) => simple_api_command(&mut socket, "PUT", c, None),
None => unreachable!(), None => unreachable!(),
} }
@@ -261,7 +435,7 @@ fn main() {
.long("api-socket") .long("api-socket")
.help("HTTP API socket path (UNIX domain socket).") .help("HTTP API socket path (UNIX domain socket).")
.takes_value(true) .takes_value(true)
.min_values(1) .number_of_values(1)
.required(true), .required(true),
) )
.subcommand( .subcommand(
@@ -273,6 +447,51 @@ fn main() {
.help(vmm::config::DeviceConfig::SYNTAX), .help(vmm::config::DeviceConfig::SYNTAX),
), ),
) )
.subcommand(
SubCommand::with_name("add-disk")
.about("Add block device")
.arg(
Arg::with_name("disk_config")
.index(1)
.help(vmm::config::DiskConfig::SYNTAX),
),
)
.subcommand(
SubCommand::with_name("add-fs")
.about("Add virtio-fs backed fs device")
.arg(
Arg::with_name("fs_config")
.index(1)
.help(vmm::config::FsConfig::SYNTAX),
),
)
.subcommand(
SubCommand::with_name("add-pmem")
.about("Add persistent memory device")
.arg(
Arg::with_name("pmem_config")
.index(1)
.help(vmm::config::PmemConfig::SYNTAX),
),
)
.subcommand(
SubCommand::with_name("add-net")
.about("Add network device")
.arg(
Arg::with_name("net_config")
.index(1)
.help(vmm::config::NetConfig::SYNTAX),
),
)
.subcommand(
SubCommand::with_name("add-vsock")
.about("Add vsock device")
.arg(
Arg::with_name("vsock_config")
.index(1)
.help(vmm::config::VsockConfig::SYNTAX),
),
)
.subcommand( .subcommand(
SubCommand::with_name("remove-device") SubCommand::with_name("remove-device")
.about("Remove VFIO device") .about("Remove VFIO device")
@@ -300,12 +519,30 @@ fn main() {
), ),
) )
.subcommand(SubCommand::with_name("resume").about("Resume the VM")) .subcommand(SubCommand::with_name("resume").about("Resume the VM"))
.subcommand(SubCommand::with_name("shutdown").about("Shutdown the VM")); .subcommand(SubCommand::with_name("shutdown").about("Shutdown the VM"))
.subcommand(
SubCommand::with_name("snapshot")
.about("Create a snapshot from VM")
.arg(
Arg::with_name("snapshot_config")
.index(1)
.help("<destination_url>"),
),
)
.subcommand(
SubCommand::with_name("restore")
.about("Restore VM from a snapshot")
.arg(
Arg::with_name("restore_config")
.index(1)
.help(vmm::config::RestoreConfig::SYNTAX),
),
);
let matches = app.get_matches(); let matches = app.get_matches();
if let Err(e) = do_command(&matches) { if let Err(e) = do_command(&matches) {
eprintln!("Error running command: {:?}", e); eprintln!("Error running command: {}", e);
process::exit(1) process::exit(1)
}; };
} }

View File

@@ -23,11 +23,7 @@ fn main() {
.arg( .arg(
Arg::with_name("block-backend") Arg::with_name("block-backend")
.long("block-backend") .long("block-backend")
.help( .help(vhost_user_block::SYNTAX)
"vhost-user-block backend parameters \
\"image=<image_path>,sock=<socket_path>,num_queues=<number_of_queues>,\
readonly=true|false,direct=true|false,poll_queue=true|false\"",
)
.takes_value(true) .takes_value(true)
.min_values(1), .min_values(1),
) )

View File

@@ -10,21 +10,23 @@ extern crate vhost_user_backend;
extern crate vm_virtio; extern crate vm_virtio;
use clap::{App, Arg}; use clap::{App, Arg};
use epoll;
use futures::executor::{ThreadPool, ThreadPoolBuilder}; use futures::executor::{ThreadPool, ThreadPoolBuilder};
use libc::EFD_NONBLOCK; use libc::EFD_NONBLOCK;
use log::*; use log::*;
use seccomp::SeccompAction;
use std::num::Wrapping; use std::num::Wrapping;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, Mutex, RwLock};
use std::{convert, error, fmt, io, process}; use std::{convert, error, fmt, io, process};
use vhost_rs::vhost_user::message::*; use vhost_rs::vhost_user::message::*;
use vhost_rs::vhost_user::SlaveFsCacheReq; use vhost_rs::vhost_user::{Listener, SlaveFsCacheReq};
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring}; use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring};
use vhost_user_fs::descriptor_utils::Error as VufDescriptorError; use vhost_user_fs::descriptor_utils::Error as VufDescriptorError;
use vhost_user_fs::descriptor_utils::{Reader, Writer}; use vhost_user_fs::descriptor_utils::{Reader, Writer};
use vhost_user_fs::filesystem::FileSystem; use vhost_user_fs::filesystem::FileSystem;
use vhost_user_fs::passthrough::{self, PassthroughFs}; use vhost_user_fs::passthrough::{self, PassthroughFs};
use vhost_user_fs::sandbox::Sandbox;
use vhost_user_fs::seccomp::enable_seccomp;
use vhost_user_fs::server::Server; use vhost_user_fs::server::Server;
use vhost_user_fs::Error as VhostUserFsError; use vhost_user_fs::Error as VhostUserFsError;
use virtio_bindings::bindings::virtio_net::*; use virtio_bindings::bindings::virtio_net::*;
@@ -83,7 +85,7 @@ impl convert::From<Error> for io::Error {
} }
} }
struct VhostUserFsBackend<F: FileSystem + Send + Sync + 'static> { struct VhostUserFsThread<F: FileSystem + Send + Sync + 'static> {
mem: Option<GuestMemoryAtomic<GuestMemoryMmap>>, mem: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
kill_evt: EventFd, kill_evt: EventFd,
server: Arc<Server<F>>, server: Arc<Server<F>>,
@@ -93,9 +95,9 @@ struct VhostUserFsBackend<F: FileSystem + Send + Sync + 'static> {
pool: ThreadPool, pool: ThreadPool,
} }
impl<F: FileSystem + Send + Sync + 'static> Clone for VhostUserFsBackend<F> { impl<F: FileSystem + Send + Sync + 'static> Clone for VhostUserFsThread<F> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
VhostUserFsBackend { VhostUserFsThread {
mem: self.mem.clone(), mem: self.mem.clone(),
kill_evt: self.kill_evt.try_clone().unwrap(), kill_evt: self.kill_evt.try_clone().unwrap(),
server: self.server.clone(), server: self.server.clone(),
@@ -106,9 +108,9 @@ impl<F: FileSystem + Send + Sync + 'static> Clone for VhostUserFsBackend<F> {
} }
} }
impl<F: FileSystem + Send + Sync + 'static> VhostUserFsBackend<F> { impl<F: FileSystem + Send + Sync + 'static> VhostUserFsThread<F> {
fn new(fs: F, thread_pool_size: usize) -> Result<Self> { fn new(fs: F, thread_pool_size: usize) -> Result<Self> {
Ok(VhostUserFsBackend { Ok(VhostUserFsThread {
mem: None, mem: None,
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?, kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?,
server: Arc::new(Server::new(fs)), server: Arc::new(Server::new(fs)),
@@ -160,8 +162,9 @@ impl<F: FileSystem + Send + Sync + 'static> VhostUserFsBackend<F> {
let mut vring = vring_lock.write().unwrap(); let mut vring = vring_lock.write().unwrap();
if event_idx { if event_idx {
if let Some(used_idx) = vring.mut_queue().add_used(&mem, head_index, 0) { let queue = vring.mut_queue();
if vring.needs_notification(&mem, Wrapping(used_idx)) { if let Some(used_idx) = queue.add_used(&mem, head_index, 0) {
if queue.needs_notification(&mem, Wrapping(used_idx)) {
vring.signal_used_queue().unwrap(); vring.signal_used_queue().unwrap();
} }
} }
@@ -176,6 +179,17 @@ impl<F: FileSystem + Send + Sync + 'static> VhostUserFsBackend<F> {
} }
} }
struct VhostUserFsBackend<F: FileSystem + Send + Sync + 'static> {
thread: Mutex<VhostUserFsThread<F>>,
}
impl<F: FileSystem + Send + Sync + 'static> VhostUserFsBackend<F> {
fn new(fs: F, thread_pool_size: usize) -> Result<Self> {
let thread = Mutex::new(VhostUserFsThread::new(fs, thread_pool_size)?);
Ok(VhostUserFsBackend { thread })
}
}
impl<F: FileSystem + Send + Sync + 'static> VhostUserBackend for VhostUserFsBackend<F> { impl<F: FileSystem + Send + Sync + 'static> VhostUserBackend for VhostUserFsBackend<F> {
fn num_queues(&self) -> usize { fn num_queues(&self) -> usize {
NUM_QUEUES NUM_QUEUES
@@ -197,25 +211,27 @@ impl<F: FileSystem + Send + Sync + 'static> VhostUserBackend for VhostUserFsBack
} }
fn set_event_idx(&mut self, enabled: bool) { fn set_event_idx(&mut self, enabled: bool) {
self.event_idx = enabled; self.thread.lock().unwrap().event_idx = enabled;
} }
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> { fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
self.mem = Some(GuestMemoryAtomic::new(mem)); self.thread.lock().unwrap().mem = Some(GuestMemoryAtomic::new(mem));
Ok(()) Ok(())
} }
fn handle_event( fn handle_event(
&mut self, &self,
device_event: u16, device_event: u16,
evset: epoll::Events, evset: epoll::Events,
vrings: &[Arc<RwLock<Vring>>], vrings: &[Arc<RwLock<Vring>>],
_thread_id: usize,
) -> VhostUserBackendResult<bool> { ) -> VhostUserBackendResult<bool> {
if evset != epoll::Events::EPOLLIN { if evset != epoll::Events::EPOLLIN {
return Err(Error::HandleEventNotEpollIn.into()); return Err(Error::HandleEventNotEpollIn.into());
} }
let mem = match &self.mem { let mut thread = self.thread.lock().unwrap();
let mem = match &thread.mem {
Some(m) => m.memory(), Some(m) => m.memory(),
None => return Err(Error::NoMemoryConfigured.into()), None => return Err(Error::NoMemoryConfigured.into()),
}; };
@@ -232,7 +248,7 @@ impl<F: FileSystem + Send + Sync + 'static> VhostUserBackend for VhostUserFsBack
_ => return Err(Error::HandleEventUnknownEvent.into()), _ => return Err(Error::HandleEventUnknownEvent.into()),
}; };
if self.event_idx { if thread.event_idx {
// vm-virtio's Queue implementation only checks avail_index // vm-virtio's Queue implementation only checks avail_index
// once, so to properly support EVENT_IDX we need to keep // once, so to properly support EVENT_IDX we need to keep
// calling process_queue() until it stops finding new // calling process_queue() until it stops finding new
@@ -242,24 +258,27 @@ impl<F: FileSystem + Send + Sync + 'static> VhostUserBackend for VhostUserFsBack
let mut vring = vring_lock.write().unwrap(); let mut vring = vring_lock.write().unwrap();
vring.mut_queue().update_avail_event(&mem); vring.mut_queue().update_avail_event(&mem);
} }
if !self.process_queue(vring_lock.clone())? { if !thread.process_queue(vring_lock.clone())? {
break; break;
} }
} }
} else { } else {
// Without EVENT_IDX, a single call is enough. // Without EVENT_IDX, a single call is enough.
self.process_queue(vring_lock)?; thread.process_queue(vring_lock)?;
} }
Ok(false) Ok(false)
} }
fn exit_event(&self) -> Option<(EventFd, Option<u16>)> { fn exit_event(&self, _thread_index: usize) -> Option<(EventFd, Option<u16>)> {
Some((self.kill_evt.try_clone().unwrap(), Some(KILL_EVENT))) Some((
self.thread.lock().unwrap().kill_evt.try_clone().unwrap(),
Some(KILL_EVENT),
))
} }
fn set_slave_req_fd(&mut self, vu_req: SlaveFsCacheReq) { fn set_slave_req_fd(&mut self, vu_req: SlaveFsCacheReq) {
self.vu_req = Some(vu_req); self.thread.lock().unwrap().vu_req = Some(vu_req);
} }
} }
@@ -278,6 +297,13 @@ fn main() {
.arg( .arg(
Arg::with_name("sock") Arg::with_name("sock")
.long("sock") .long("sock")
.help("vhost-user socket path (deprecated)")
.takes_value(true)
.min_values(1),
)
.arg(
Arg::with_name("socket")
.long("socket")
.help("vhost-user socket path") .help("vhost-user socket path")
.takes_value(true) .takes_value(true)
.min_values(1), .min_values(1),
@@ -294,42 +320,86 @@ fn main() {
.long("disable-xattr") .long("disable-xattr")
.help("Disable support for extended attributes"), .help("Disable support for extended attributes"),
) )
.arg(
Arg::with_name("disable-sandbox")
.long("disable-sandbox")
.help("Don't set up a sandbox for the daemon"),
)
.arg(
Arg::with_name("seccomp")
.long("seccomp")
.help("Disable/debug seccomp security")
.possible_values(&["kill", "log", "trap", "none"])
.default_value("kill"),
)
.get_matches(); .get_matches();
// Retrieve arguments // Retrieve arguments
let shared_dir = cmd_arguments let shared_dir = cmd_arguments
.value_of("shared-dir") .value_of("shared-dir")
.expect("Failed to retrieve shared directory path"); .expect("Failed to retrieve shared directory path");
let sock = cmd_arguments let socket = match cmd_arguments.value_of("socket") {
.value_of("sock") Some(path) => path,
.expect("Failed to retrieve vhost-user socket path"); None => {
println!("warning: use of deprecated parameter '--sock': Please use the '--socket' option instead.");
cmd_arguments
.value_of("sock")
.expect("Failed to retrieve vhost-user socket path")
}
};
let thread_pool_size: usize = match cmd_arguments.value_of("thread-pool-size") { let thread_pool_size: usize = match cmd_arguments.value_of("thread-pool-size") {
Some(size) => size.parse().expect("Invalid argument for thread-pool-size"), Some(size) => size.parse().expect("Invalid argument for thread-pool-size"),
None => THREAD_POOL_SIZE, None => THREAD_POOL_SIZE,
}; };
let xattr: bool = !cmd_arguments.is_present("disable-xattr"); let xattr: bool = !cmd_arguments.is_present("disable-xattr");
let create_sandbox: bool = !cmd_arguments.is_present("disable-sandbox");
// Convert into appropriate types let seccomp_mode: SeccompAction = match cmd_arguments.value_of("seccomp").unwrap() {
let sock = String::from(sock); "none" => SeccompAction::Allow, // i.e. no seccomp
"kill" => SeccompAction::Kill,
let fs_cfg = passthrough::Config { "log" => SeccompAction::Log,
root_dir: shared_dir.to_string(), "trap" => SeccompAction::Trap,
xattr, _ => unreachable!(), // We told Arg possible_values
..Default::default()
}; };
let listener = Listener::new(socket, true).unwrap();
let fs_cfg = if create_sandbox {
let mut sandbox = Sandbox::new(shared_dir.to_string());
match sandbox.enter().unwrap() {
Some(child_pid) => {
unsafe { libc::waitpid(child_pid, std::ptr::null_mut(), 0) };
return;
}
None => passthrough::Config {
root_dir: "/".to_string(),
xattr,
proc_sfd_rawfd: sandbox.get_proc_self_fd(),
..Default::default()
},
}
} else {
passthrough::Config {
root_dir: shared_dir.to_string(),
xattr,
..Default::default()
}
};
// Must happen before we start the thread pool
if seccomp_mode != SeccompAction::Allow {
enable_seccomp(seccomp_mode).unwrap();
};
let fs = PassthroughFs::new(fs_cfg).unwrap(); let fs = PassthroughFs::new(fs_cfg).unwrap();
let fs_backend = Arc::new(RwLock::new( let fs_backend = Arc::new(RwLock::new(
VhostUserFsBackend::new(fs, thread_pool_size).unwrap(), VhostUserFsBackend::new(fs, thread_pool_size).unwrap(),
)); ));
let mut daemon = VhostUserDaemon::new( let mut daemon =
String::from("vhost-user-fs-backend"), VhostUserDaemon::new(String::from("vhost-user-fs-backend"), fs_backend.clone()).unwrap();
sock,
fs_backend.clone(),
)
.unwrap();
if let Err(e) = daemon.start() { if let Err(e) = daemon.start(listener) {
error!("Failed to start daemon: {:?}", e); error!("Failed to start daemon: {:?}", e);
process::exit(1); process::exit(1);
} }
@@ -338,7 +408,15 @@ fn main() {
error!("Waiting for daemon failed: {:?}", e); error!("Waiting for daemon failed: {:?}", e);
} }
let kill_evt = &fs_backend.read().unwrap().kill_evt; let kill_evt = fs_backend
.read()
.unwrap()
.thread
.lock()
.unwrap()
.kill_evt
.try_clone()
.unwrap();
if let Err(e) = kill_evt.write(1) { if let Err(e) = kill_evt.write(1) {
error!("Error shutting down worker thread: {:?}", e) error!("Error shutting down worker thread: {:?}", e)
} }

View File

@@ -21,11 +21,7 @@ fn main() {
.arg( .arg(
Arg::with_name("net-backend") Arg::with_name("net-backend")
.long("net-backend") .long("net-backend")
.help( .help(vhost_user_net::SYNTAX)
"vhost-user-net backend parameters \
\"ip=<ip_addr>,mask=<net_mask>,sock=<socket_path>,\
num_queues=<number_of_queues>,queue_size=<size_of_each_queue>,tap=<if_name>\"",
)
.takes_value(true) .takes_value(true)
.min_values(1), .min_values(1),
) )

584
src/main.rs Executable file → Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -74,7 +74,7 @@ write_files:
bash -c "echo 1af4 1041 > /sys/bus/pci/drivers/vfio-pci/new_id" bash -c "echo 1af4 1041 > /sys/bus/pci/drivers/vfio-pci/new_id"
bash -c "echo 0000:00:06.0 > /sys/bus/pci/devices/0000\:00\:06.0/driver/unbind" bash -c "echo 0000:00:06.0 > /sys/bus/pci/devices/0000\:00\:06.0/driver/unbind"
bash -c "echo 1af4 1041 > /sys/bus/pci/drivers/vfio-pci/new_id" bash -c "echo 1af4 1041 > /sys/bus/pci/drivers/vfio-pci/new_id"
# 512M ram requires 256 pages # 1G ram requires 512 pages
echo 256 | sudo tee /proc/sys/vm/nr_hugepages echo 512 | sudo tee /proc/sys/vm/nr_hugepages
sudo chmod a+rwX /dev/hugepages sudo chmod a+rwX /dev/hugepages
/mnt/cloud-hypervisor --kernel /mnt/vmlinux --cmdline "console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=PARTUUID=6fb4d1a8-6c8c-4dd7-9f7c-1fe0b9f2574c VFIOTAG" --disk path=/mnt/clear-31311-cloudguest.img path=/mnt/cloudinit.img --cpus boot=1 --memory size=512M,file=/dev/hugepages --device path=/sys/bus/pci/devices/0000:00:05.0/ path=/sys/bus/pci/devices/0000:00:06.0/ --api-socket /tmp/ch_api.sock /mnt/cloud-hypervisor --kernel /mnt/vmlinux --cmdline "console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=PARTUUID=6fb4d1a8-6c8c-4dd7-9f7c-1fe0b9f2574c VFIOTAG" --disk path=/mnt/clear-31311-cloudguest.img path=/mnt/cloudinit.img --cpus boot=1 --memory size=512M,hotplug_size=1G,file=/dev/hugepages --device path=/sys/bus/pci/devices/0000:00:05.0/ path=/sys/bus/pci/devices/0000:00:06.0/ --api-socket /tmp/ch_api.sock

0
test_data/cloud-init/ubuntu/meta-data Executable file → Normal file
View File

0
test_data/cloud-init/ubuntu/user-data Executable file → Normal file
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +0,0 @@
[package]
name = "vfio"
version = "0.0.1"
authors = ["The Cloud Hypervisor Authors"]
[dependencies]
arc-swap = ">=0.4.4"
byteorder = "1.3.4"
devices = { path = "../devices" }
kvm-bindings = "0.2.0"
kvm-ioctls = "0.5.0"
libc = "0.2.68"
log = "0.4.8"
pci = { path = "../pci" }
vm-allocator = { path = "../vm-allocator" }
vm-device = { path = "../vm-device" }
vm-memory = { version = "0.1.0", features = ["backend-mmap"] }
vmm-sys-util = ">=0.3.1"
[dependencies.vfio-bindings]
git = "https://github.com/rust-vmm/vfio-bindings"
features = ["fam-wrappers"]

View File

@@ -1,61 +0,0 @@
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
//
//#![deny(missing_docs)]
//! Virtual Function I/O (VFIO) API
extern crate arc_swap;
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;
extern crate vm_allocator;
extern crate vm_device;
extern crate vm_memory;
#[macro_use]
extern crate vmm_sys_util;
mod vfio_device;
mod vfio_ioctls;
mod vfio_pci;
use std::mem::size_of;
pub use vfio_device::{VfioContainer, VfioDevice, VfioDmaMapping, VfioError};
pub use vfio_pci::{VfioPciDevice, VfioPciError};
// Returns a `Vec<T>` with a size in bytes at least as large as `size_in_bytes`.
fn vec_with_size_in_bytes<T: Default>(size_in_bytes: usize) -> Vec<T> {
let rounded_size = (size_in_bytes + size_of::<T>() - 1) / size_of::<T>();
let mut v = Vec::with_capacity(rounded_size);
for _ in 0..rounded_size {
v.push(T::default())
}
v
}
// The kvm API has many structs that resemble the following `Foo` structure:
//
// ```
// #[repr(C)]
// struct Foo {
// some_data: u32
// entries: __IncompleteArrayField<__u32>,
// }
// ```
//
// In order to allocate such a structure, `size_of::<Foo>()` would be too small because it would not
// include any space for `entries`. To make the allocation large enough while still being aligned
// for `Foo`, a `Vec<Foo>` is created. Only the first element of `Vec<Foo>` would actually be used
// as a `Foo`. The remaining memory in the `Vec<Foo>` is for `entries`, which must be contiguous
// with `Foo`. This function is used to make the `Vec<Foo>` with enough space for `count` entries.
pub fn vec_with_array_field<T: Default, F>(count: usize) -> Vec<T> {
let element_space = count * size_of::<F>();
let vec_size_bytes = size_of::<T>() + element_space;
vec_with_size_in_bytes(vec_size_bytes)
}

View File

@@ -1,902 +0,0 @@
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
//
use crate::vec_with_array_field;
use byteorder::{ByteOrder, LittleEndian};
use kvm_ioctls::*;
use std::collections::HashMap;
use std::convert::TryInto;
use std::ffi::CString;
use std::fmt;
use std::fs::{File, OpenOptions};
use std::io;
use std::mem;
use std::num;
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::os::unix::prelude::FileExt;
use std::path::{Path, PathBuf};
use std::result;
use std::sync::Arc;
use std::u32;
use vfio_bindings::bindings::vfio::*;
use vfio_bindings::bindings::IrqSet;
use vfio_ioctls::*;
use vm_device::{get_host_address_range, ExternalDmaMapping};
use vm_memory::{
Address, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, GuestMemoryMmap,
GuestMemoryRegion,
};
use vmm_sys_util::eventfd::EventFd;
use vmm_sys_util::fam::FamStruct;
use vmm_sys_util::ioctl::*;
#[derive(Debug)]
pub enum VfioError {
OpenContainer(io::Error),
OpenGroup(io::Error),
GetGroupStatus,
GroupViable,
VfioApiVersion,
VfioExtension,
VfioInvalidType,
VfioType1V2,
GroupSetContainer,
UnsetContainer,
ContainerSetIOMMU,
GroupGetDeviceFD,
KvmSetDeviceAttr(kvm_ioctls::Error),
VfioDeviceGetInfo,
VfioDeviceGetRegionInfo,
InvalidPath,
IommuDmaMap,
IommuDmaUnmap,
VfioDeviceGetIrqInfo,
VfioDeviceSetIrq,
VfioNoIommuGroup(String),
ReadLink(io::Error),
ParseInt(num::ParseIntError),
}
pub type Result<T> = std::result::Result<T, VfioError>;
impl fmt::Display for VfioError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
VfioError::OpenContainer(e) => {
write!(f, "failed to open /dev/vfio/vfio container: {}", e)
}
VfioError::OpenGroup(e) => {
write!(f, "failed to open /dev/vfio/$group_num group: {}", e)
}
VfioError::GetGroupStatus => write!(f, "failed to get Group Status"),
VfioError::GroupViable => write!(f, "group is inviable"),
VfioError::VfioApiVersion => write!(
f,
"vfio API version doesn't match with VFIO_API_VERSION defined in vfio-bindings"
),
VfioError::VfioExtension => write!(f, "failed to check VFIO extension"),
VfioError::VfioInvalidType => write!(f, "invalid VFIO type"),
VfioError::VfioType1V2 => {
write!(f, "container dones't support VfioType1V2 IOMMU driver type")
}
VfioError::GroupSetContainer => {
write!(f, "failed to add vfio group into vfio container")
}
VfioError::UnsetContainer => write!(f, "failed to unset vfio container"),
VfioError::ContainerSetIOMMU => write!(
f,
"failed to set container's IOMMU driver type as VfioType1V2"
),
VfioError::GroupGetDeviceFD => write!(f, "failed to get vfio device fd"),
VfioError::KvmSetDeviceAttr(e) => {
write!(f, "failed to set KVM vfio device's attribute: {}", e)
}
VfioError::VfioDeviceGetInfo => {
write!(f, "failed to get vfio device's info or info doesn't match")
}
VfioError::VfioDeviceGetRegionInfo => {
write!(f, "failed to get vfio device's region info")
}
VfioError::InvalidPath => write!(f, "invalid file path"),
VfioError::IommuDmaMap => write!(f, "failed to add guest memory map into iommu table"),
VfioError::IommuDmaUnmap => {
write!(f, "failed to remove guest memory map from iommu table")
}
VfioError::VfioDeviceGetIrqInfo => write!(f, "failed to get vfio device irq info"),
VfioError::VfioDeviceSetIrq => write!(f, "failed to set vfio deviece irq"),
VfioError::ReadLink(e) => write!(f, "failed to read link from path: {}", e),
VfioError::VfioNoIommuGroup(e) => write!(f, "Failed to find iommu_group for: {}", e),
VfioError::ParseInt(e) => write!(f, "failed to parse integer: {}", e),
}
}
}
#[repr(C)]
#[derive(Debug, Default)]
struct vfio_region_info_with_cap {
region_info: vfio_region_info,
cap_info: __IncompleteArrayField<u8>,
}
pub struct VfioContainer {
container: File,
}
impl VfioContainer {
fn new() -> Result<Self> {
let container = OpenOptions::new()
.read(true)
.write(true)
.open("/dev/vfio/vfio")
.map_err(VfioError::OpenContainer)?;
Ok(VfioContainer { container })
}
fn get_api_version(&self) -> i32 {
// Safe as file is vfio container fd and ioctl is defined by kernel.
unsafe { ioctl(self, VFIO_GET_API_VERSION()) }
}
fn check_extension(&self, val: u32) -> Result<()> {
if val != VFIO_TYPE1_IOMMU && val != VFIO_TYPE1v2_IOMMU {
return Err(VfioError::VfioInvalidType);
}
// Safe as file is vfio container and make sure val is valid.
let ret = unsafe { ioctl_with_val(self, VFIO_CHECK_EXTENSION(), val.into()) };
if ret != 1 {
return Err(VfioError::VfioExtension);
}
Ok(())
}
fn set_iommu(&self, val: u32) -> Result<()> {
if val != VFIO_TYPE1_IOMMU && val != VFIO_TYPE1v2_IOMMU {
return Err(VfioError::VfioInvalidType);
}
// Safe as file is vfio container and make sure val is valid.
let ret = unsafe { ioctl_with_val(self, VFIO_SET_IOMMU(), val.into()) };
if ret < 0 {
return Err(VfioError::ContainerSetIOMMU);
}
Ok(())
}
pub fn vfio_dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<()> {
let dma_map = vfio_iommu_type1_dma_map {
argsz: mem::size_of::<vfio_iommu_type1_dma_map>() as u32,
flags: VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE,
vaddr: user_addr,
iova,
size,
};
// Safe as file is vfio container, dma_map is constructed by us, and
// we check the return value
let ret = unsafe { ioctl_with_ref(self, VFIO_IOMMU_MAP_DMA(), &dma_map) };
if ret != 0 {
return Err(VfioError::IommuDmaMap);
}
Ok(())
}
pub fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<()> {
let mut dma_unmap = vfio_iommu_type1_dma_unmap {
argsz: mem::size_of::<vfio_iommu_type1_dma_unmap>() as u32,
flags: 0,
iova,
size,
};
// Safe as file is vfio container, dma_unmap is constructed by us, and
// we check the return value
let ret = unsafe { ioctl_with_mut_ref(self, VFIO_IOMMU_UNMAP_DMA(), &mut dma_unmap) };
if ret != 0 || dma_unmap.size != size {
return Err(VfioError::IommuDmaUnmap);
}
Ok(())
}
}
impl AsRawFd for VfioContainer {
fn as_raw_fd(&self) -> RawFd {
self.container.as_raw_fd()
}
}
struct VfioGroup {
group: File,
device: Arc<DeviceFd>,
container: Arc<VfioContainer>,
}
impl VfioGroup {
fn new(id: u32, device: Arc<DeviceFd>) -> Result<Self> {
let group_path = Path::new("/dev/vfio").join(id.to_string());
let group = OpenOptions::new()
.read(true)
.write(true)
.open(&group_path)
.map_err(VfioError::OpenGroup)?;
let mut group_status = vfio_group_status {
argsz: mem::size_of::<vfio_group_status>() as u32,
flags: 0,
};
// Safe as we are the owner of group and group_status which are valid value.
let mut ret =
unsafe { ioctl_with_mut_ref(&group, VFIO_GROUP_GET_STATUS(), &mut group_status) };
if ret < 0 {
return Err(VfioError::GetGroupStatus);
}
if group_status.flags != VFIO_GROUP_FLAGS_VIABLE {
return Err(VfioError::GroupViable);
}
let container = Arc::new(VfioContainer::new()?);
if container.get_api_version() as u32 != VFIO_API_VERSION {
return Err(VfioError::VfioApiVersion);
}
container.check_extension(VFIO_TYPE1v2_IOMMU)?;
// Safe as we are the owner of group and container_raw_fd which are valid value,
// and we verify the ret value
let container_raw_fd = container.as_raw_fd();
ret = unsafe { ioctl_with_ref(&group, VFIO_GROUP_SET_CONTAINER(), &container_raw_fd) };
if ret < 0 {
return Err(VfioError::GroupSetContainer);
}
container.set_iommu(VFIO_TYPE1v2_IOMMU)?;
Self::kvm_device_add_group(&device, &group)?;
Ok(VfioGroup {
group,
device,
container,
})
}
fn kvm_device_add_group(device_fd: &Arc<DeviceFd>, group: &File) -> Result<()> {
let group_fd = group.as_raw_fd();
let group_fd_ptr = &group_fd as *const i32;
let dev_attr = kvm_bindings::kvm_device_attr {
flags: 0,
group: kvm_bindings::KVM_DEV_VFIO_GROUP,
attr: u64::from(kvm_bindings::KVM_DEV_VFIO_GROUP_ADD),
addr: group_fd_ptr as u64,
};
device_fd
.set_device_attr(&dev_attr)
.map_err(VfioError::KvmSetDeviceAttr)
}
fn kvm_device_del_group(&self) -> std::result::Result<(), kvm_ioctls::Error> {
let group_fd = self.as_raw_fd();
let group_fd_ptr = &group_fd as *const i32;
let dev_attr = kvm_bindings::kvm_device_attr {
flags: 0,
group: kvm_bindings::KVM_DEV_VFIO_GROUP,
attr: u64::from(kvm_bindings::KVM_DEV_VFIO_GROUP_DEL),
addr: group_fd_ptr as u64,
};
self.device.set_device_attr(&dev_attr)
}
fn unset_container(&self) -> std::result::Result<(), io::Error> {
let container_raw_fd = self.container.as_raw_fd();
// Safe as we are the owner of self and container_raw_fd which are valid value.
let ret = unsafe { ioctl_with_ref(self, VFIO_GROUP_UNSET_CONTAINER(), &container_raw_fd) };
if ret < 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
fn get_device(&self, name: &Path) -> Result<VfioDeviceInfo> {
let uuid_osstr = name.file_name().ok_or(VfioError::InvalidPath)?;
let uuid_str = uuid_osstr.to_str().ok_or(VfioError::InvalidPath)?;
let path: CString = CString::new(uuid_str.as_bytes()).expect("CString::new() failed");
let path_ptr = path.as_ptr();
// Safe as we are the owner of self and path_ptr which are valid value.
let fd = unsafe { ioctl_with_ptr(self, VFIO_GROUP_GET_DEVICE_FD(), path_ptr) };
if fd < 0 {
return Err(VfioError::GroupGetDeviceFD);
}
// Safe as fd is valid FD
let device = unsafe { File::from_raw_fd(fd) };
let mut dev_info = vfio_device_info {
argsz: mem::size_of::<vfio_device_info>() as u32,
flags: 0,
num_regions: 0,
num_irqs: 0,
};
// Safe as we are the owner of dev and dev_info which are valid value,
// and we verify the return value.
let ret = unsafe { ioctl_with_mut_ref(&device, VFIO_DEVICE_GET_INFO(), &mut dev_info) };
if ret < 0
|| (dev_info.flags & VFIO_DEVICE_FLAGS_PCI) == 0
|| dev_info.num_regions < VFIO_PCI_CONFIG_REGION_INDEX + 1
|| dev_info.num_irqs < VFIO_PCI_MSIX_IRQ_INDEX + 1
{
return Err(VfioError::VfioDeviceGetInfo);
}
Ok(VfioDeviceInfo {
device,
flags: dev_info.flags,
num_regions: dev_info.num_regions,
num_irqs: dev_info.num_irqs,
})
}
}
impl AsRawFd for VfioGroup {
fn as_raw_fd(&self) -> RawFd {
self.group.as_raw_fd()
}
}
impl Drop for VfioGroup {
fn drop(&mut self) {
match self.kvm_device_del_group() {
Ok(_) => {}
Err(e) => {
error!("Could not delete VFIO group: {:?}", e);
}
}
match self.unset_container() {
Ok(_) => {}
Err(e) => {
error!("Could not unset container: {:?}", e);
}
}
}
}
struct VfioRegion {
flags: u32,
size: u64,
offset: u64,
mmap: (u64, u64),
}
struct VfioIrq {
flags: u32,
index: u32,
count: u32,
}
struct VfioDeviceInfo {
device: File,
flags: u32,
num_regions: u32,
num_irqs: u32,
}
impl VfioDeviceInfo {
fn get_irqs(&self) -> Result<HashMap<u32, VfioIrq>> {
let mut irqs: HashMap<u32, VfioIrq> = HashMap::new();
for index in 0..self.num_irqs {
let mut irq_info = vfio_irq_info {
argsz: mem::size_of::<vfio_irq_info>() as u32,
flags: 0,
index,
count: 0,
};
let ret = unsafe {
ioctl_with_mut_ref(&self.device, VFIO_DEVICE_GET_IRQ_INFO(), &mut irq_info)
};
if ret < 0 {
warn!("Could not get VFIO IRQ info for index {:}", index);
continue;
}
let irq = VfioIrq {
flags: irq_info.flags,
index,
count: irq_info.count,
};
debug!("IRQ #{}", index);
debug!("\tflag 0x{:x}", irq.flags);
debug!("\tindex {}", irq.index);
debug!("\tcount {}", irq.count);
irqs.insert(index, irq);
}
Ok(irqs)
}
fn get_regions(&self) -> Result<Vec<VfioRegion>> {
let mut regions: Vec<VfioRegion> = Vec::new();
for i in VFIO_PCI_BAR0_REGION_INDEX..self.num_regions {
let argsz: u32 = mem::size_of::<vfio_region_info>() as u32;
let mut reg_info = vfio_region_info {
argsz,
flags: 0,
index: i,
cap_offset: 0,
size: 0,
offset: 0,
};
// Safe as we are the owner of dev and reg_info which are valid value,
// and we verify the return value.
let mut ret = unsafe {
ioctl_with_mut_ref(&self.device, VFIO_DEVICE_GET_REGION_INFO(), &mut reg_info)
};
if ret < 0 {
warn!("Could not get region #{} info", i);
continue;
}
let mut mmap_size: u64 = reg_info.size;
let mut mmap_offset: u64 = 0;
if reg_info.flags & VFIO_REGION_INFO_FLAG_CAPS != 0 && reg_info.argsz > argsz {
let cap_len: usize = (reg_info.argsz - argsz) as usize;
let mut region_with_cap =
vec_with_array_field::<vfio_region_info_with_cap, u8>(cap_len);
region_with_cap[0].region_info.argsz = reg_info.argsz;
region_with_cap[0].region_info.flags = 0;
region_with_cap[0].region_info.index = i;
region_with_cap[0].region_info.cap_offset = 0;
region_with_cap[0].region_info.size = 0;
region_with_cap[0].region_info.offset = 0;
// Safe as we are the owner of dev and region_info which are valid value,
// and we verify the return value.
ret = unsafe {
ioctl_with_mut_ref(
&self.device,
VFIO_DEVICE_GET_REGION_INFO(),
&mut (region_with_cap[0].region_info),
)
};
if ret < 0 {
warn!("Could not get region #{} info", i);
continue;
}
// region_with_cap[0].cap_info may contain vfio_region_info_cap_sparse_mmap
// struct or vfio_region_info_cap_type struct. Both of them begin with
// vfio_info_cap_header.
// so safe to convert cap_info into vfio_info_cap_header pointer first, and
// safe to access its elments through this poiner.
#[allow(clippy::cast_ptr_alignment)]
let cap_header =
unsafe { region_with_cap[0].cap_info.as_ptr() as *const vfio_info_cap_header };
if unsafe { u32::from((*cap_header).id) } == VFIO_REGION_INFO_CAP_SPARSE_MMAP {
// cap_info is vfio_region_sparse_mmap here
// so safe to convert cap_info into vfio_info_region_sparse_mmap pointer, and
// safe to access its elements through this pointer.
#[allow(clippy::cast_ptr_alignment)]
let sparse_mmap = unsafe {
region_with_cap[0].cap_info.as_ptr()
as *const vfio_region_info_cap_sparse_mmap
};
let mmap_area = unsafe {
(*sparse_mmap).areas.as_ptr() as *const vfio_region_sparse_mmap_area
};
mmap_size = unsafe { (*mmap_area).size };
mmap_offset = unsafe { (*mmap_area).offset };
}
}
let region = VfioRegion {
flags: reg_info.flags,
size: reg_info.size,
offset: reg_info.offset,
mmap: (mmap_offset, mmap_size),
};
debug!("Region #{}", i);
debug!("\tflag 0x{:x}", region.flags);
debug!("\tsize 0x{:x}", region.size);
debug!("\toffset 0x{:x}", region.offset);
regions.push(region);
}
Ok(regions)
}
}
/// This structure implements the ExternalDmaMapping trait. It is meant to
/// be used when the caller tries to provide a way to update the mappings
/// associated with a specific VFIO container.
pub struct VfioDmaMapping {
container: Arc<VfioContainer>,
memory: GuestMemoryAtomic<GuestMemoryMmap>,
}
impl VfioDmaMapping {
pub fn new(container: Arc<VfioContainer>, memory: GuestMemoryAtomic<GuestMemoryMmap>) -> Self {
VfioDmaMapping { container, memory }
}
}
impl ExternalDmaMapping for VfioDmaMapping {
fn map(&self, iova: u64, gpa: u64, size: u64) -> result::Result<(), io::Error> {
let user_addr = if let Some(addr) = get_host_address_range(
&self.memory.memory(),
GuestAddress(gpa),
size.try_into().unwrap(),
) {
addr as u64
} else {
return Err(io::Error::new(
io::ErrorKind::Other,
format!(
"failed to convert guest address 0x{:x} into \
host user virtual address",
gpa
),
));
};
self.container
.vfio_dma_map(iova, size, user_addr)
.map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!(
"failed to map memory for VFIO container, \
iova 0x{:x}, gpa 0x{:x}, size 0x{:x}: {:?}",
iova, gpa, size, e
),
)
})
}
fn unmap(&self, iova: u64, size: u64) -> result::Result<(), io::Error> {
self.container.vfio_dma_unmap(iova, size).map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!(
"failed to unmap memory for VFIO container, \
iova 0x{:x}, size 0x{:x}: {:?}",
iova, size, e
),
)
})
}
}
/// Vfio device for exposing regions which could be read/write to kernel vfio device.
pub struct VfioDevice {
device: File,
flags: u32,
group: VfioGroup,
regions: Vec<VfioRegion>,
irqs: HashMap<u32, VfioIrq>,
mem: GuestMemoryAtomic<GuestMemoryMmap>,
iommu_attached: bool,
}
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: GuestMemoryAtomic<GuestMemoryMmap>,
iommu_attached: bool,
) -> Result<Self> {
let uuid_path: PathBuf = [sysfspath, Path::new("iommu_group")].iter().collect();
if !uuid_path.exists() {
return Err(VfioError::VfioNoIommuGroup(
uuid_path.to_str().unwrap().to_string(),
));
}
let group_path = uuid_path.read_link().map_err(VfioError::ReadLink)?;
let group_osstr = group_path.file_name().ok_or(VfioError::InvalidPath)?;
let group_str = group_osstr.to_str().ok_or(VfioError::InvalidPath)?;
let group_id = group_str.parse::<u32>().map_err(VfioError::ParseInt)?;
let group = VfioGroup::new(group_id, device_fd)?;
let device_info = group.get_device(sysfspath)?;
let regions = device_info.get_regions()?;
let irqs = device_info.get_irqs()?;
Ok(VfioDevice {
device: device_info.device,
flags: device_info.flags,
group,
regions,
irqs,
mem,
iommu_attached,
})
}
/// VFIO device reset.
/// Only if the device supports being reset.
pub fn reset(&self) {
if self.flags & VFIO_DEVICE_FLAGS_RESET != 0 {
unsafe { ioctl(self, VFIO_DEVICE_RESET()) };
}
}
/// Enables a VFIO device IRQs.
/// This maps a vector of EventFds to all VFIO managed interrupts. In other words, this
/// tells VFIO which EventFd to write into whenever one of the device interrupt vector
/// is triggered.
///
/// # Arguments
///
/// * `irq_index` - The type (INTX, MSI or MSI-X) of interrupts to enable.
/// * `event_fds` - The EventFds vector that matches all the supported VFIO interrupts.
pub fn enable_irq(&self, irq_index: u32, event_fds: Vec<&EventFd>) -> Result<()> {
let irq = self
.irqs
.get(&irq_index)
.ok_or(VfioError::VfioDeviceSetIrq)?;
if irq.count == 0 {
return Err(VfioError::VfioDeviceSetIrq);
}
let mut irq_set_wrapper = IrqSet::new(event_fds.len() * mem::size_of::<u32>());
let mut irq_set = irq_set_wrapper.as_mut_fam_struct();
let fds = irq_set.as_mut_slice();
for (index, event_fd) in event_fds.iter().enumerate() {
let fds_offset = index * mem::size_of::<u32>();
let fd = &mut fds[fds_offset..fds_offset + mem::size_of::<u32>()];
LittleEndian::write_u32(fd, event_fd.as_raw_fd() as u32);
}
irq_set.argsz = mem::size_of::<vfio_irq_set>() as u32
+ (event_fds.len() * mem::size_of::<u32>()) as u32;
irq_set.flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER;
irq_set.index = irq_index;
irq_set.start = 0;
irq_set.count = irq.count;
// Safe as we are the owner of self and irq_set which are valid value
let ret = unsafe { ioctl_with_ref(self, VFIO_DEVICE_SET_IRQS(), irq_set) };
if ret < 0 {
return Err(VfioError::VfioDeviceSetIrq);
}
Ok(())
}
/// Disables a VFIO device IRQs
///
/// # Arguments
///
/// * `irq_index` - The type (INTX, MSI or MSI-X) of interrupts to disable.
pub fn disable_irq(&self, irq_index: u32) -> Result<()> {
let irq = self
.irqs
.get(&irq_index)
.ok_or(VfioError::VfioDeviceSetIrq)?;
if irq.count == 0 {
return Err(VfioError::VfioDeviceSetIrq);
}
let mut irq_set = vec_with_array_field::<vfio_irq_set, u32>(0);
irq_set[0].argsz = mem::size_of::<vfio_irq_set>() as u32;
irq_set[0].flags = VFIO_IRQ_SET_ACTION_TRIGGER | VFIO_IRQ_SET_DATA_NONE;
irq_set[0].index = irq_index;
irq_set[0].start = 0;
irq_set[0].count = 0;
// Safe as we are the owner of self and irq_set which are valid value
let ret = unsafe { ioctl_with_ref(self, VFIO_DEVICE_SET_IRQS(), &irq_set[0]) };
if ret < 0 {
return Err(VfioError::VfioDeviceSetIrq);
}
Ok(())
}
/// Wrapper to enable MSI IRQs.
pub fn enable_msi(&self, fds: Vec<&EventFd>) -> Result<()> {
self.enable_irq(VFIO_PCI_MSI_IRQ_INDEX, fds)
}
/// Wrapper to disable MSI IRQs.
pub fn disable_msi(&self) -> Result<()> {
self.disable_irq(VFIO_PCI_MSI_IRQ_INDEX)
}
/// Wrapper to enable MSI-X IRQs.
pub fn enable_msix(&self, fds: Vec<&EventFd>) -> Result<()> {
self.enable_irq(VFIO_PCI_MSIX_IRQ_INDEX, fds)
}
/// Wrapper to disable MSI-X IRQs.
pub fn disable_msix(&self) -> Result<()> {
self.disable_irq(VFIO_PCI_MSIX_IRQ_INDEX)
}
/// get a region's flag
pub fn get_region_flags(&self, index: u32) -> u32 {
match self.regions.get(index as usize) {
Some(v) => v.flags,
None => 0,
}
}
/// get a region's offset
pub fn get_region_offset(&self, index: u32) -> u64 {
match self.regions.get(index as usize) {
Some(v) => v.offset,
None => 0,
}
}
/// get a region's mmap info
pub fn get_region_mmap(&self, index: u32) -> (u64, u64) {
match self.regions.get(index as usize) {
Some(v) => v.mmap,
None => {
warn!("get_region_mmap with invalid index: {}", index);
(0, 0)
}
}
}
/// get a region's size
pub fn get_region_size(&self, index: u32) -> u64 {
match self.regions.get(index as usize) {
Some(v) => v.size,
None => {
warn!("get_region_size with invalid index: {}", index);
0
}
}
}
/// Read region's data from VFIO device into buf
/// index: region num
/// buf: data destination and buf length is read size
/// addr: offset in the region
pub fn region_read(&self, index: u32, buf: &mut [u8], addr: u64) {
let region: &VfioRegion;
match self.regions.get(index as usize) {
Some(v) => region = v,
None => {
warn!("region read with invalid index: {}", index);
return;
}
}
let size = buf.len() as u64;
if size > region.size || addr + size > region.size {
warn!(
"region read with invalid parameter, add: {}, size: {}",
addr, size
);
return;
}
if let Err(e) = self.device.read_exact_at(buf, region.offset + addr) {
warn!(
"Failed to read region in index: {}, addr: {}, error: {}",
index, addr, e
);
}
}
/// write the data from buf into a vfio device region
/// index: region num
/// buf: data src and buf length is write size
/// addr: offset in the region
pub fn region_write(&self, index: u32, buf: &[u8], addr: u64) {
let stub: &VfioRegion;
match self.regions.get(index as usize) {
Some(v) => stub = v,
None => {
warn!("region write with invalid index: {}", index);
return;
}
}
let size = buf.len() as u64;
if size > stub.size
|| addr + size > stub.size
|| (stub.flags & VFIO_REGION_INFO_FLAG_WRITE) == 0
{
warn!(
"region write with invalid parameter, add: {}, size: {}",
addr, size
);
return;
}
if let Err(e) = self.device.write_all_at(buf, stub.offset + addr) {
warn!(
"Failed to write region in index: {}, addr: {}, error: {}",
index, addr, e
);
}
}
pub fn get_container(&self) -> Arc<VfioContainer> {
self.group.container.clone()
}
fn vfio_dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<()> {
self.group.container.vfio_dma_map(iova, size, user_addr)
}
fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<()> {
self.group.container.vfio_dma_unmap(iova, size)
}
/// 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<()> {
if !self.iommu_attached {
self.mem.memory().with_regions(|_index, region| {
self.vfio_dma_map(
region.start_addr().raw_value(),
region.len() as u64,
region.as_ptr() as u64,
)
})?;
}
Ok(())
}
/// remove all guest memory regions from vfio containers iommu table
/// then vfio kernel driver couldn't access this guest memory
pub fn unset_dma_map(&self) -> Result<()> {
if !self.iommu_attached {
self.mem.memory().with_regions(|_index, region| {
self.vfio_dma_unmap(region.start_addr().raw_value(), region.len() as u64)
})?;
}
Ok(())
}
/// Return the maximum numner of interrupts a VFIO device can request.
/// This is used for pre-allocating the VFIO PCI routes.
pub fn max_interrupts(&self) -> u32 {
let mut max_interrupts = 0;
let irq_indexes = vec![
VFIO_PCI_INTX_IRQ_INDEX,
VFIO_PCI_MSI_IRQ_INDEX,
VFIO_PCI_MSIX_IRQ_INDEX,
];
for index in irq_indexes {
if let Some(irq_info) = self.irqs.get(&index) {
if irq_info.count > max_interrupts {
max_interrupts = irq_info.count;
}
}
}
max_interrupts
}
}
impl AsRawFd for VfioDevice {
fn as_raw_fd(&self) -> RawFd {
self.device.as_raw_fd()
}
}

View File

@@ -1,36 +0,0 @@
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
//
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
use vfio_bindings::bindings::vfio::*;
ioctl_io_nr!(VFIO_GET_API_VERSION, VFIO_TYPE, VFIO_BASE);
ioctl_io_nr!(VFIO_CHECK_EXTENSION, VFIO_TYPE, VFIO_BASE + 1);
ioctl_io_nr!(VFIO_SET_IOMMU, VFIO_TYPE, VFIO_BASE + 2);
ioctl_io_nr!(VFIO_GROUP_GET_STATUS, VFIO_TYPE, VFIO_BASE + 3);
ioctl_io_nr!(VFIO_GROUP_SET_CONTAINER, VFIO_TYPE, VFIO_BASE + 4);
ioctl_io_nr!(VFIO_GROUP_UNSET_CONTAINER, VFIO_TYPE, VFIO_BASE + 5);
ioctl_io_nr!(VFIO_GROUP_GET_DEVICE_FD, VFIO_TYPE, VFIO_BASE + 6);
ioctl_io_nr!(VFIO_DEVICE_GET_INFO, VFIO_TYPE, VFIO_BASE + 7);
ioctl_io_nr!(VFIO_DEVICE_GET_REGION_INFO, VFIO_TYPE, VFIO_BASE + 8);
ioctl_io_nr!(VFIO_DEVICE_GET_IRQ_INFO, VFIO_TYPE, VFIO_BASE + 9);
ioctl_io_nr!(VFIO_DEVICE_SET_IRQS, VFIO_TYPE, VFIO_BASE + 10);
ioctl_io_nr!(VFIO_DEVICE_RESET, VFIO_TYPE, VFIO_BASE + 11);
ioctl_io_nr!(
VFIO_DEVICE_GET_PCI_HOT_RESET_INFO,
VFIO_TYPE,
VFIO_BASE + 12
);
ioctl_io_nr!(VFIO_DEVICE_PCI_HOT_RESET, VFIO_TYPE, VFIO_BASE + 13);
ioctl_io_nr!(VFIO_DEVICE_QUERY_GFX_PLANE, VFIO_TYPE, VFIO_BASE + 14);
ioctl_io_nr!(VFIO_DEVICE_GET_GFX_DMABUF, VFIO_TYPE, VFIO_BASE + 15);
ioctl_io_nr!(VFIO_DEVICE_IOEVENTFD, VFIO_TYPE, VFIO_BASE + 16);
ioctl_io_nr!(VFIO_IOMMU_GET_INFO, VFIO_TYPE, VFIO_BASE + 12);
ioctl_io_nr!(VFIO_IOMMU_MAP_DMA, VFIO_TYPE, VFIO_BASE + 13);
ioctl_io_nr!(VFIO_IOMMU_UNMAP_DMA, VFIO_TYPE, VFIO_BASE + 14);
ioctl_io_nr!(VFIO_IOMMU_ENABLE, VFIO_TYPE, VFIO_BASE + 15);
ioctl_io_nr!(VFIO_IOMMU_DISABLE, VFIO_TYPE, VFIO_BASE + 16);

View File

@@ -11,10 +11,10 @@ mmio_support = ["vm-virtio/mmio_support"]
[dependencies] [dependencies]
epoll = ">=4.0.1" epoll = ">=4.0.1"
libc = "0.2.68" libc = "0.2.71"
log = "0.4.8" log = "0.4.8"
virtio-bindings = "0.1.0" virtio-bindings = "0.1.0"
vm-memory = "0.1.0" vm-memory = "0.2.1"
vm-virtio = { path = "../vm-virtio" } vm-virtio = { path = "../vm-virtio" }
vmm-sys-util = ">=0.3.1" vmm-sys-util = ">=0.3.1"
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] } vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }

View File

@@ -20,7 +20,7 @@ use vhost_rs::vhost_user::message::{
VhostUserVirtioFeatures, VhostUserVringAddrFlags, VhostUserVringState, VhostUserVirtioFeatures, VhostUserVringAddrFlags, VhostUserVringState,
}; };
use vhost_rs::vhost_user::{ use vhost_rs::vhost_user::{
Error as VhostUserError, Result as VhostUserResult, SlaveFsCacheReq, SlaveListener, Error as VhostUserError, Listener, Result as VhostUserResult, SlaveFsCacheReq, SlaveListener,
VhostUserSlaveReqHandler, VhostUserSlaveReqHandler,
}; };
use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
@@ -64,9 +64,12 @@ pub trait VhostUserBackend: Send + Sync + 'static {
/// Depth of each queue. /// Depth of each queue.
fn max_queue_size(&self) -> usize; fn max_queue_size(&self) -> usize;
/// Virtio features. /// Available virtio features.
fn features(&self) -> u64; fn features(&self) -> u64;
/// Acked virtio features.
fn acked_features(&mut self, _features: u64) {}
/// Virtio protocol features. /// Virtio protocol features.
fn protocol_features(&self) -> VhostUserProtocolFeatures; fn protocol_features(&self) -> VhostUserProtocolFeatures;
@@ -81,10 +84,11 @@ pub trait VhostUserBackend: Send + Sync + 'static {
/// virtqueues on its own, but does not know what to do with events /// virtqueues on its own, but does not know what to do with events
/// happening on custom listeners. /// happening on custom listeners.
fn handle_event( fn handle_event(
&mut self, &self,
device_event: u16, device_event: u16,
evset: epoll::Events, evset: epoll::Events,
vrings: &[Arc<RwLock<Vring>>], vrings: &[Arc<RwLock<Vring>>],
thread_id: usize,
) -> result::Result<bool, io::Error>; ) -> result::Result<bool, io::Error>;
/// Get virtio device configuration. /// Get virtio device configuration.
@@ -105,7 +109,7 @@ pub trait VhostUserBackend: Send + Sync + 'static {
/// When this EventFd is written to the worker thread will exit. An optional id may /// When this EventFd is written to the worker thread will exit. An optional id may
/// also be provided, if it not provided then the exit event will be first event id /// also be provided, if it not provided then the exit event will be first event id
/// after the last queue /// after the last queue
fn exit_event(&self) -> Option<(EventFd, Option<u16>)> { fn exit_event(&self, _thread_index: usize) -> Option<(EventFd, Option<u16>)> {
None None
} }
@@ -113,13 +117,16 @@ pub trait VhostUserBackend: Send + Sync + 'static {
/// A default implementation is provided as we cannot expect all backends /// A default implementation is provided as we cannot expect all backends
/// to implement this function. /// to implement this function.
fn set_slave_req_fd(&mut self, _vu_req: SlaveFsCacheReq) {} fn set_slave_req_fd(&mut self, _vu_req: SlaveFsCacheReq) {}
fn queues_per_thread(&self) -> Vec<u64> {
vec![0xffff_ffff]
}
} }
/// This structure is the public API the backend is allowed to interact with /// This structure is the public API the backend is allowed to interact with
/// in order to run a fully functional vhost-user daemon. /// in order to run a fully functional vhost-user daemon.
pub struct VhostUserDaemon<S: VhostUserBackend> { pub struct VhostUserDaemon<S: VhostUserBackend> {
name: String, name: String,
sock_path: String,
handler: Arc<Mutex<VhostUserHandler<S>>>, handler: Arc<Mutex<VhostUserHandler<S>>>,
main_thread: Option<thread::JoinHandle<Result<()>>>, main_thread: Option<thread::JoinHandle<Result<()>>>,
} }
@@ -131,14 +138,13 @@ impl<S: VhostUserBackend> VhostUserDaemon<S> {
/// listening onto registered event. Those events can be vring events or /// listening onto registered event. Those events can be vring events or
/// custom events from the backend, but they get to be registered later /// custom events from the backend, but they get to be registered later
/// during the sequence. /// during the sequence.
pub fn new(name: String, sock_path: String, backend: Arc<RwLock<S>>) -> Result<Self> { pub fn new(name: String, backend: Arc<RwLock<S>>) -> Result<Self> {
let handler = Arc::new(Mutex::new( let handler = Arc::new(Mutex::new(
VhostUserHandler::new(backend).map_err(Error::NewVhostUserHandler)?, VhostUserHandler::new(backend).map_err(Error::NewVhostUserHandler)?,
)); ));
Ok(VhostUserDaemon { Ok(VhostUserDaemon {
name, name,
sock_path,
handler, handler,
main_thread: None, main_thread: None,
}) })
@@ -148,10 +154,9 @@ impl<S: VhostUserBackend> VhostUserDaemon<S> {
/// all requests coming through this socket. This runs in an infinite loop /// all requests coming through this socket. This runs in an infinite loop
/// that should be terminating once the other end of the socket (the VMM) /// that should be terminating once the other end of the socket (the VMM)
/// disconnects. /// disconnects.
pub fn start(&mut self) -> Result<()> { pub fn start(&mut self, listener: Listener) -> Result<()> {
let mut slave_listener = let mut slave_listener = SlaveListener::new(listener, self.handler.clone())
SlaveListener::new(self.sock_path.as_str(), true, self.handler.clone()) .map_err(Error::CreateSlaveListener)?;
.map_err(Error::CreateSlaveListener)?;
let mut slave_handler = slave_listener let mut slave_handler = slave_listener
.accept() .accept()
.map_err(Error::CreateSlaveReqHandler)? .map_err(Error::CreateSlaveReqHandler)?
@@ -187,8 +192,8 @@ impl<S: VhostUserBackend> VhostUserDaemon<S> {
/// Retrieve the vring worker. This is necessary to perform further /// Retrieve the vring worker. This is necessary to perform further
/// actions like registering and unregistering some extra event file /// actions like registering and unregistering some extra event file
/// descriptors. /// descriptors.
pub fn get_vring_worker(&self) -> Arc<VringWorker> { pub fn get_vring_workers(&self) -> Vec<Arc<VringWorker>> {
self.handler.lock().unwrap().get_vring_worker() self.handler.lock().unwrap().get_vring_workers()
} }
} }
@@ -208,8 +213,6 @@ pub struct Vring {
call: Option<EventFd>, call: Option<EventFd>,
err: Option<EventFd>, err: Option<EventFd>,
enabled: bool, enabled: bool,
event_idx: bool,
signalled_used: Option<Wrapping<u16>>,
} }
impl Vring { impl Vring {
@@ -220,8 +223,6 @@ impl Vring {
call: None, call: None,
err: None, err: None,
enabled: false, enabled: false,
event_idx: false,
signalled_used: None,
} }
} }
@@ -229,31 +230,6 @@ impl Vring {
&mut self.queue &mut self.queue
} }
pub fn set_event_idx(&mut self, enabled: bool) {
/* Also reset the last signalled event */
self.signalled_used = None;
self.event_idx = enabled;
}
pub fn needs_notification(&mut self, mem: &GuestMemoryMmap, used_idx: Wrapping<u16>) -> bool {
if !self.event_idx {
return true;
}
let mut notify = true;
if let Some(old_idx) = self.signalled_used {
if let Some(used_event) = self.mut_queue().get_used_event(&mem) {
if (used_idx - used_event - Wrapping(1u16)) >= (used_idx - old_idx) {
notify = false;
}
}
}
self.signalled_used = Some(used_idx);
notify
}
pub fn signal_used_queue(&mut self) -> result::Result<(), io::Error> { pub fn signal_used_queue(&mut self) -> result::Result<(), io::Error> {
if let Some(call) = self.call.as_ref() { if let Some(call) = self.call.as_ref() {
call.write(1) call.write(1)
@@ -283,6 +259,7 @@ struct VringEpollHandler<S: VhostUserBackend> {
backend: Arc<RwLock<S>>, backend: Arc<RwLock<S>>,
vrings: Vec<Arc<RwLock<Vring>>>, vrings: Vec<Arc<RwLock<Vring>>>,
exit_event_id: Option<u16>, exit_event_id: Option<u16>,
thread_id: usize,
} }
impl<S: VhostUserBackend> VringEpollHandler<S> { impl<S: VhostUserBackend> VringEpollHandler<S> {
@@ -310,9 +287,9 @@ impl<S: VhostUserBackend> VringEpollHandler<S> {
} }
self.backend self.backend
.write() .read()
.unwrap() .unwrap()
.handle_event(device_event, evset, &self.vrings) .handle_event(device_event, evset, &self.vrings, self.thread_id)
.map_err(VringEpollHandlerError::HandleEventBackendHandling) .map_err(VringEpollHandlerError::HandleEventBackendHandling)
} }
} }
@@ -330,7 +307,13 @@ enum VringWorkerError {
type VringWorkerResult<T> = std::result::Result<T, VringWorkerError>; type VringWorkerResult<T> = std::result::Result<T, VringWorkerError>;
pub struct VringWorker { pub struct VringWorker {
epoll_fd: RawFd, epoll_file: File,
}
impl AsRawFd for VringWorker {
fn as_raw_fd(&self) -> RawFd {
self.epoll_file.as_raw_fd()
}
} }
impl VringWorker { impl VringWorker {
@@ -339,7 +322,7 @@ impl VringWorker {
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
'epoll: loop { 'epoll: loop {
let num_events = match epoll::wait(self.epoll_fd, -1, &mut events[..]) { let num_events = match epoll::wait(self.epoll_file.as_raw_fd(), -1, &mut events[..]) {
Ok(res) => res, Ok(res) => res,
Err(e) => { Err(e) => {
if e.kind() == io::ErrorKind::Interrupted { if e.kind() == io::ErrorKind::Interrupted {
@@ -393,7 +376,7 @@ impl VringWorker {
data: u64, data: u64,
) -> result::Result<(), io::Error> { ) -> result::Result<(), io::Error> {
epoll::ctl( epoll::ctl(
self.epoll_fd, self.epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_ADD, epoll::ControlOptions::EPOLL_CTL_ADD,
fd, fd,
epoll::Event::new(ev_type, data), epoll::Event::new(ev_type, data),
@@ -410,7 +393,7 @@ impl VringWorker {
data: u64, data: u64,
) -> result::Result<(), io::Error> { ) -> result::Result<(), io::Error> {
epoll::ctl( epoll::ctl(
self.epoll_fd, self.epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_DEL, epoll::ControlOptions::EPOLL_CTL_DEL,
fd, fd,
epoll::Event::new(ev_type, data), epoll::Event::new(ev_type, data),
@@ -453,22 +436,24 @@ type VhostUserHandlerResult<T> = std::result::Result<T, VhostUserHandlerError>;
struct VhostUserHandler<S: VhostUserBackend> { struct VhostUserHandler<S: VhostUserBackend> {
backend: Arc<RwLock<S>>, backend: Arc<RwLock<S>>,
worker: Arc<VringWorker>, workers: Vec<Arc<VringWorker>>,
owned: bool, owned: bool,
features_acked: bool, features_acked: bool,
acked_features: u64, acked_features: u64,
acked_protocol_features: u64, acked_protocol_features: u64,
num_queues: usize, num_queues: usize,
max_queue_size: usize, max_queue_size: usize,
queues_per_thread: Vec<u64>,
memory: Option<Memory>, memory: Option<Memory>,
vrings: Vec<Arc<RwLock<Vring>>>, vrings: Vec<Arc<RwLock<Vring>>>,
worker_thread: Option<thread::JoinHandle<VringWorkerResult<()>>>, worker_threads: Vec<thread::JoinHandle<VringWorkerResult<()>>>,
} }
impl<S: VhostUserBackend> VhostUserHandler<S> { impl<S: VhostUserBackend> VhostUserHandler<S> {
fn new(backend: Arc<RwLock<S>>) -> VhostUserHandlerResult<Self> { fn new(backend: Arc<RwLock<S>>) -> VhostUserHandlerResult<Self> {
let num_queues = backend.read().unwrap().num_queues(); let num_queues = backend.read().unwrap().num_queues();
let max_queue_size = backend.read().unwrap().max_queue_size(); let max_queue_size = backend.read().unwrap().max_queue_size();
let queues_per_thread = backend.read().unwrap().queues_per_thread();
let mut vrings: Vec<Arc<RwLock<Vring>>> = Vec::new(); let mut vrings: Vec<Arc<RwLock<Vring>>> = Vec::new();
for _ in 0..num_queues { for _ in 0..num_queues {
@@ -476,14 +461,20 @@ impl<S: VhostUserBackend> VhostUserHandler<S> {
vrings.push(vring); vrings.push(vring);
} }
// Create the epoll file descriptor let mut workers = Vec::new();
let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?; let mut worker_threads = Vec::new();
for (thread_id, queues_mask) in queues_per_thread.iter().enumerate() {
// Create the epoll file descriptor
let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?;
// Use 'File' to enforce closing on 'epoll_fd'
let epoll_file = unsafe { File::from_raw_fd(epoll_fd) };
let vring_worker = Arc::new(VringWorker { epoll_fd }); let vring_worker = Arc::new(VringWorker { epoll_file });
let worker = vring_worker.clone(); let worker = vring_worker.clone();
let exit_event_id = let exit_event_id = if let Some((exit_event_fd, exit_event_id)) =
if let Some((exit_event_fd, exit_event_id)) = backend.read().unwrap().exit_event() { backend.read().unwrap().exit_event(thread_id)
{
let exit_event_id = exit_event_id.unwrap_or(num_queues as u16); let exit_event_id = exit_event_id.unwrap_or(num_queues as u16);
worker worker
.register_listener( .register_listener(
@@ -497,36 +488,47 @@ impl<S: VhostUserBackend> VhostUserHandler<S> {
None None
}; };
let vring_handler = VringEpollHandler { let mut thread_vrings: Vec<Arc<RwLock<Vring>>> = Vec::new();
backend: backend.clone(), for (index, vring) in vrings.iter().enumerate() {
vrings: vrings.clone(), if (queues_mask >> index) & 1u64 == 1u64 {
exit_event_id, thread_vrings.push(vring.clone());
}; }
}
let worker_thread = Some( let vring_handler = VringEpollHandler {
thread::Builder::new() backend: backend.clone(),
vrings: thread_vrings,
exit_event_id,
thread_id,
};
let worker_thread = thread::Builder::new()
.name("vring_worker".to_string()) .name("vring_worker".to_string())
.spawn(move || vring_worker.run(vring_handler)) .spawn(move || vring_worker.run(vring_handler))
.map_err(VhostUserHandlerError::SpawnVringWorker)?, .map_err(VhostUserHandlerError::SpawnVringWorker)?;
);
workers.push(worker);
worker_threads.push(worker_thread);
}
Ok(VhostUserHandler { Ok(VhostUserHandler {
backend, backend,
worker, workers,
owned: false, owned: false,
features_acked: false, features_acked: false,
acked_features: 0, acked_features: 0,
acked_protocol_features: 0, acked_protocol_features: 0,
num_queues, num_queues,
max_queue_size, max_queue_size,
queues_per_thread,
memory: None, memory: None,
vrings, vrings,
worker_thread, worker_threads,
}) })
} }
fn get_vring_worker(&self) -> Arc<VringWorker> { fn get_vring_workers(&self) -> Vec<Arc<VringWorker>> {
self.worker.clone() self.workers.clone()
} }
fn vmm_va_to_gpa(&self, vmm_va: u64) -> VhostUserHandlerResult<u64> { fn vmm_va_to_gpa(&self, vmm_va: u64) -> VhostUserHandlerResult<u64> {
@@ -584,6 +586,11 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
vring.write().unwrap().enabled = vring_enabled; vring.write().unwrap().enabled = vring_enabled;
} }
self.backend
.write()
.unwrap()
.acked_features(self.acked_features);
Ok(()) Ok(())
} }
@@ -702,6 +709,7 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
self.vrings[index as usize] self.vrings[index as usize]
.write() .write()
.unwrap() .unwrap()
.mut_queue()
.set_event_idx(event_idx); .set_event_idx(event_idx);
self.backend.write().unwrap().set_event_idx(event_idx); self.backend.write().unwrap().set_event_idx(event_idx);
Ok(()) Ok(())
@@ -718,9 +726,20 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
// VHOST_USER_GET_VRING_BASE. // VHOST_USER_GET_VRING_BASE.
self.vrings[index as usize].write().unwrap().queue.ready = false; self.vrings[index as usize].write().unwrap().queue.ready = false;
if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() { if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() {
self.worker for (thread_index, queues_mask) in self.queues_per_thread.iter().enumerate() {
.unregister_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(index)) let shifted_queues_mask = queues_mask >> index;
.map_err(VhostUserError::ReqHandlerError)?; if shifted_queues_mask & 1u64 == 1u64 {
let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones();
self.workers[thread_index]
.unregister_listener(
fd.as_raw_fd(),
epoll::Events::EPOLLIN,
u64::from(evt_idx),
)
.map_err(VhostUserError::ReqHandlerError)?;
break;
}
}
} }
let next_avail = self.vrings[index as usize] let next_avail = self.vrings[index as usize]
@@ -752,9 +771,20 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
// VHOST_USER_GET_VRING_BASE. // VHOST_USER_GET_VRING_BASE.
self.vrings[index as usize].write().unwrap().queue.ready = true; self.vrings[index as usize].write().unwrap().queue.ready = true;
if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() { if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() {
self.worker for (thread_index, queues_mask) in self.queues_per_thread.iter().enumerate() {
.register_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(index)) let shifted_queues_mask = queues_mask >> index;
.map_err(VhostUserError::ReqHandlerError)?; if shifted_queues_mask & 1u64 == 1u64 {
let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones();
self.workers[thread_index]
.register_listener(
fd.as_raw_fd(),
epoll::Events::EPOLLIN,
u64::from(evt_idx),
)
.map_err(VhostUserError::ReqHandlerError)?;
break;
}
}
} }
Ok(()) Ok(())
@@ -837,7 +867,7 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
impl<S: VhostUserBackend> Drop for VhostUserHandler<S> { impl<S: VhostUserBackend> Drop for VhostUserHandler<S> {
fn drop(&mut self) { fn drop(&mut self) {
if let Some(thread) = self.worker_thread.take() { for thread in self.worker_threads.drain(..) {
if let Err(e) = thread.join() { if let Err(e) = thread.join() {
error!("Error in vring worker: {:?}", e); error!("Error in vring worker: {:?}", e);
} }

View File

@@ -7,12 +7,13 @@ edition = "2018"
[dependencies] [dependencies]
bitflags = "1.1.0" bitflags = "1.1.0"
epoll = ">=4.0.1" epoll = ">=4.0.1"
libc = "0.2.68" libc = "0.2.71"
log = "0.4.8" log = "0.4.8"
qcow = { path = "../qcow" } qcow = { path = "../qcow" }
vhost_user_backend = { path = "../vhost_user_backend" } vhost_user_backend = { path = "../vhost_user_backend" }
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] } vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
virtio-bindings = "0.1.0" virtio-bindings = "0.1.0"
vm-memory = "0.1.0" vm-memory = "0.2.1"
vm-virtio = { path = "../vm-virtio" } vm-virtio = { path = "../vm-virtio" }
vmm = { path = "../vmm" }
vmm-sys-util = ">=0.3.1" vmm-sys-util = ">=0.3.1"

View File

@@ -13,7 +13,6 @@ extern crate vhost_rs;
extern crate vhost_user_backend; extern crate vhost_user_backend;
extern crate vm_virtio; extern crate vm_virtio;
use epoll;
use libc::EFD_NONBLOCK; use libc::EFD_NONBLOCK;
use log::*; use log::*;
use qcow::{self, ImageType, QcowFile}; use qcow::{self, ImageType, QcowFile};
@@ -21,25 +20,29 @@ use std::fs::File;
use std::fs::OpenOptions; use std::fs::OpenOptions;
use std::io::Read; use std::io::Read;
use std::io::{Seek, SeekFrom, Write}; use std::io::{Seek, SeekFrom, Write};
use std::mem;
use std::num::Wrapping; use std::num::Wrapping;
use std::ops::DerefMut;
use std::os::unix::fs::OpenOptionsExt; use std::os::unix::fs::OpenOptionsExt;
use std::path::PathBuf; use std::path::PathBuf;
use std::process; use std::process;
use std::slice; use std::result;
use std::sync::{Arc, RwLock}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, RwLock};
use std::time::Instant; use std::time::Instant;
use std::vec::Vec; use std::vec::Vec;
use std::{convert, error, fmt, io}; use std::{convert, error, fmt, io};
use vhost_rs::vhost_user::message::*; use vhost_rs::vhost_user::message::*;
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring, VringWorker}; use vhost_rs::vhost_user::Listener;
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring};
use virtio_bindings::bindings::virtio_blk::*; use virtio_bindings::bindings::virtio_blk::*;
use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
use vm_memory::{Bytes, GuestMemoryError, GuestMemoryMmap}; use vm_memory::ByteValued;
use vm_memory::{Bytes, GuestMemoryMmap};
use vm_virtio::block::{build_disk_image_id, Request}; use vm_virtio::block::{build_disk_image_id, Request};
use vm_virtio::VirtioBlockConfig;
use vmm::config::{OptionParser, OptionParserError, Toggle};
use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::eventfd::EventFd;
const QUEUE_SIZE: usize = 1024;
const SECTOR_SHIFT: u8 = 9; const SECTOR_SHIFT: u8 = 9;
const SECTOR_SIZE: u64 = (0x01 as u64) << SECTOR_SHIFT; const SECTOR_SIZE: u64 = (0x01 as u64) << SECTOR_SHIFT;
const BLK_SIZE: u32 = 512; const BLK_SIZE: u32 = 512;
@@ -51,37 +54,30 @@ const POLL_QUEUE_US: u128 = 50;
trait DiskFile: Read + Seek + Write + Send + Sync {} trait DiskFile: Read + Seek + Write + Send + Sync {}
impl<D: Read + Seek + Write + Send + Sync> DiskFile for D {} impl<D: Read + Seek + Write + Send + Sync> DiskFile for D {}
pub type Result<T> = std::result::Result<T, Error>; type Result<T> = std::result::Result<T, Error>;
pub type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>; type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>;
#[derive(Debug)] #[derive(Debug)]
pub enum Error { enum Error {
/// Failed to detect image type.
DetectImageType,
/// Bad memory address.
GuestMemory(GuestMemoryError),
/// Can't open image file.
OpenImage,
/// Failed to parse direct parameter.
ParseDirectParam,
/// Failed to parse image parameter.
ParseImageParam,
/// Failed to parse sock parameter.
ParseSockParam,
/// Failed to parse readonly parameter.
ParseReadOnlyParam,
/// Failed parsing fs number of queues parameter.
ParseBlkNumQueuesParam(std::num::ParseIntError),
/// Failed to parse the poll_queue parameter.
ParsePollQueueParam,
/// Failed to handle event other than input event.
HandleEventNotEpollIn,
/// Failed to create kill eventfd /// Failed to create kill eventfd
CreateKillEventFd(io::Error), CreateKillEventFd(io::Error),
/// Failed to parse configuration string
FailedConfigParse(OptionParserError),
/// Failed to handle event other than input event.
HandleEventNotEpollIn,
/// Failed to handle unknown event. /// Failed to handle unknown event.
HandleEventUnknownEvent, HandleEventUnknownEvent,
/// No path provided
PathParameterMissing,
/// No socket provided
SocketParameterMissing,
} }
pub const SYNTAX: &str = "vhost-user-block backend parameters \
\"path=<image_path>,socket=<socket_path>,num_queues=<number_of_queues>,\
queue_size=<size_of_each_queue>,readonly=true|false,direct=true|false,\
poll_queue=true|false\"";
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "vhost_user_block_error: {:?}", self) write!(f, "vhost_user_block_error: {:?}", self)
@@ -96,70 +92,35 @@ impl convert::From<Error> for io::Error {
} }
} }
pub struct VhostUserBlkBackend { struct VhostUserBlkThread {
mem: Option<GuestMemoryMmap>, mem: Option<GuestMemoryMmap>,
vring_worker: Option<Arc<VringWorker>>, disk_image: Arc<Mutex<dyn DiskFile>>,
disk_image: Box<dyn DiskFile>,
disk_image_id: Vec<u8>, disk_image_id: Vec<u8>,
disk_nsectors: u64, disk_nsectors: u64,
config: virtio_blk_config,
rdonly: bool,
poll_queue: bool,
event_idx: bool, event_idx: bool,
kill_evt: EventFd, kill_evt: EventFd,
writeback: Arc<AtomicBool>,
} }
impl VhostUserBlkBackend { impl VhostUserBlkThread {
pub fn new( fn new(
image_path: String, disk_image: Arc<Mutex<dyn DiskFile>>,
num_queues: usize, disk_image_id: Vec<u8>,
rdonly: bool, disk_nsectors: u64,
direct: bool, writeback: Arc<AtomicBool>,
poll_queue: bool,
) -> Result<Self> { ) -> Result<Self> {
let mut options = OpenOptions::new(); Ok(VhostUserBlkThread {
options.read(true);
options.write(!rdonly);
if direct {
options.custom_flags(libc::O_DIRECT);
}
let image: File = options.open(&image_path).unwrap();
let mut raw_img: vm_virtio::RawFile = vm_virtio::RawFile::new(image, direct);
let image_id = build_disk_image_id(&PathBuf::from(&image_path));
let image_type = qcow::detect_image_type(&mut raw_img).unwrap();
let mut image = match image_type {
ImageType::Raw => Box::new(raw_img) as Box<dyn DiskFile>,
ImageType::Qcow2 => Box::new(QcowFile::from(raw_img).unwrap()) as Box<dyn DiskFile>,
};
let nsectors = (image.seek(SeekFrom::End(0)).unwrap() as u64) / SECTOR_SIZE;
let mut config = virtio_blk_config::default();
config.capacity = nsectors;
config.blk_size = BLK_SIZE;
config.size_max = 65535;
config.seg_max = 128 - 2;
config.min_io_size = 1;
config.opt_io_size = 1;
config.num_queues = num_queues as u16;
config.wce = 1;
Ok(VhostUserBlkBackend {
mem: None, mem: None,
vring_worker: None, disk_image,
disk_image: image, disk_image_id,
disk_image_id: image_id, disk_nsectors,
disk_nsectors: nsectors,
config,
rdonly,
poll_queue,
event_idx: false, event_idx: false,
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?, kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?,
writeback,
}) })
} }
pub fn process_queue(&mut self, vring: &mut Vring) -> bool { fn process_queue(&mut self, vring: &mut Vring) -> bool {
let mut used_any = false; let mut used_any = false;
let mem = match self.mem.as_ref() { let mem = match self.mem.as_ref() {
Some(m) => m, Some(m) => m,
@@ -170,10 +131,11 @@ impl VhostUserBlkBackend {
debug!("got an element in the queue"); debug!("got an element in the queue");
let len; let len;
match Request::parse(&head, mem) { match Request::parse(&head, mem) {
Ok(request) => { Ok(mut request) => {
debug!("element is a valid request"); debug!("element is a valid request");
request.set_writeback(self.writeback.load(Ordering::SeqCst));
let status = match request.execute( let status = match request.execute(
&mut self.disk_image, &mut self.disk_image.lock().unwrap().deref_mut(),
self.disk_nsectors, self.disk_nsectors,
mem, mem,
&self.disk_image_id, &self.disk_image_id,
@@ -196,8 +158,9 @@ impl VhostUserBlkBackend {
} }
if self.event_idx { if self.event_idx {
if let Some(used_idx) = vring.mut_queue().add_used(mem, head.index, len) { let queue = vring.mut_queue();
if vring.needs_notification(&mem, Wrapping(used_idx)) { if let Some(used_idx) = queue.add_used(mem, head.index, len) {
if queue.needs_notification(&mem, Wrapping(used_idx)) {
debug!("signalling queue"); debug!("signalling queue");
vring.signal_used_queue().unwrap(); vring.signal_used_queue().unwrap();
} else { } else {
@@ -215,9 +178,103 @@ impl VhostUserBlkBackend {
used_any used_any
} }
}
pub fn set_vring_worker(&mut self, vring_worker: Option<Arc<VringWorker>>) { struct VhostUserBlkBackend {
self.vring_worker = vring_worker; threads: Vec<Mutex<VhostUserBlkThread>>,
config: VirtioBlockConfig,
rdonly: bool,
poll_queue: bool,
queues_per_thread: Vec<u64>,
queue_size: usize,
acked_features: u64,
writeback: Arc<AtomicBool>,
}
impl VhostUserBlkBackend {
fn new(
image_path: String,
num_queues: usize,
rdonly: bool,
direct: bool,
poll_queue: bool,
queue_size: usize,
) -> Result<Self> {
let mut options = OpenOptions::new();
options.read(true);
options.write(!rdonly);
if direct {
options.custom_flags(libc::O_DIRECT);
}
let image: File = options.open(&image_path).unwrap();
let mut raw_img: vm_virtio::RawFile = vm_virtio::RawFile::new(image, direct);
let image_id = build_disk_image_id(&PathBuf::from(&image_path));
let image_type = qcow::detect_image_type(&mut raw_img).unwrap();
let image = match image_type {
ImageType::Raw => Arc::new(Mutex::new(raw_img)) as Arc<Mutex<dyn DiskFile>>,
ImageType::Qcow2 => {
Arc::new(Mutex::new(QcowFile::from(raw_img).unwrap())) as Arc<Mutex<dyn DiskFile>>
}
};
let nsectors = (image.lock().unwrap().seek(SeekFrom::End(0)).unwrap() as u64) / SECTOR_SIZE;
let mut config = VirtioBlockConfig::default();
config.capacity = nsectors;
config.blk_size = BLK_SIZE;
config.size_max = 65535;
config.seg_max = 128 - 2;
config.min_io_size = 1;
config.opt_io_size = 1;
config.num_queues = num_queues as u16;
config.writeback = 1;
let mut queues_per_thread = Vec::new();
let mut threads = Vec::new();
let writeback = Arc::new(AtomicBool::new(true));
for i in 0..num_queues {
let thread = Mutex::new(VhostUserBlkThread::new(
image.clone(),
image_id.clone(),
nsectors,
writeback.clone(),
)?);
threads.push(thread);
queues_per_thread.push(0b1 << i);
}
Ok(VhostUserBlkBackend {
threads,
config,
rdonly,
poll_queue,
queues_per_thread,
queue_size,
acked_features: 0,
writeback,
})
}
fn update_writeback(&mut self) {
// Use writeback from config if VIRTIO_BLK_F_CONFIG_WCE
let writeback =
if self.acked_features & 1 << VIRTIO_BLK_F_CONFIG_WCE == 1 << VIRTIO_BLK_F_CONFIG_WCE {
self.config.writeback == 1
} else {
// Else check if VIRTIO_BLK_F_FLUSH negotiated
self.acked_features & 1 << VIRTIO_BLK_F_FLUSH == 1 << VIRTIO_BLK_F_FLUSH
};
info!(
"Changing cache mode to {}",
if writeback {
"writeback"
} else {
"writethrough"
}
);
self.writeback.store(writeback, Ordering::SeqCst);
} }
} }
@@ -227,7 +284,7 @@ impl VhostUserBackend for VhostUserBlkBackend {
} }
fn max_queue_size(&self) -> usize { fn max_queue_size(&self) -> usize {
QUEUE_SIZE self.queue_size as usize
} }
fn features(&self) -> u64 { fn features(&self) -> u64 {
@@ -235,6 +292,7 @@ impl VhostUserBackend for VhostUserBlkBackend {
| 1 << VIRTIO_BLK_F_CONFIG_WCE | 1 << VIRTIO_BLK_F_CONFIG_WCE
| 1 << VIRTIO_RING_F_EVENT_IDX | 1 << VIRTIO_RING_F_EVENT_IDX
| 1 << VIRTIO_F_VERSION_1 | 1 << VIRTIO_F_VERSION_1
| 1 << VIRTIO_BLK_F_FLUSH
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(); | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
if self.rdonly { if self.rdonly {
@@ -243,24 +301,34 @@ impl VhostUserBackend for VhostUserBlkBackend {
avail_features avail_features
} }
fn acked_features(&mut self, features: u64) {
self.acked_features = features;
self.update_writeback();
}
fn protocol_features(&self) -> VhostUserProtocolFeatures { fn protocol_features(&self) -> VhostUserProtocolFeatures {
VhostUserProtocolFeatures::CONFIG VhostUserProtocolFeatures::CONFIG
} }
fn set_event_idx(&mut self, enabled: bool) { fn set_event_idx(&mut self, enabled: bool) {
self.event_idx = enabled; for thread in self.threads.iter() {
thread.lock().unwrap().event_idx = enabled;
}
} }
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> { fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
self.mem = Some(mem); for thread in self.threads.iter() {
thread.lock().unwrap().mem = Some(mem.clone());
}
Ok(()) Ok(())
} }
fn handle_event( fn handle_event(
&mut self, &self,
device_event: u16, device_event: u16,
evset: epoll::Events, evset: epoll::Events,
vrings: &[Arc<RwLock<Vring>>], vrings: &[Arc<RwLock<Vring>>],
thread_id: usize,
) -> VhostUserBackendResult<bool> { ) -> VhostUserBackendResult<bool> {
if evset != epoll::Events::EPOLLIN { if evset != epoll::Events::EPOLLIN {
return Err(Error::HandleEventNotEpollIn.into()); return Err(Error::HandleEventNotEpollIn.into());
@@ -268,16 +336,17 @@ impl VhostUserBackend for VhostUserBlkBackend {
debug!("event received: {:?}", device_event); debug!("event received: {:?}", device_event);
let mut thread = self.threads[thread_id].lock().unwrap();
match device_event { match device_event {
q if device_event < self.config.num_queues => { 0 => {
let mut vring = vrings[q as usize].write().unwrap(); let mut vring = vrings[0].write().unwrap();
if self.poll_queue { if self.poll_queue {
// Actively poll the queue until POLL_QUEUE_US has passed // Actively poll the queue until POLL_QUEUE_US has passed
// without seeing a new request. // without seeing a new request.
let mut now = Instant::now(); let mut now = Instant::now();
loop { loop {
if self.process_queue(&mut vring) { if thread.process_queue(&mut vring) {
now = Instant::now(); now = Instant::now();
} else if now.elapsed().as_micros() > POLL_QUEUE_US { } else if now.elapsed().as_micros() > POLL_QUEUE_US {
break; break;
@@ -285,7 +354,7 @@ impl VhostUserBackend for VhostUserBlkBackend {
} }
} }
if self.event_idx { if thread.event_idx {
// vm-virtio's Queue implementation only checks avail_index // vm-virtio's Queue implementation only checks avail_index
// once, so to properly support EVENT_IDX we need to keep // once, so to properly support EVENT_IDX we need to keep
// calling process_queue() until it stops finding new // calling process_queue() until it stops finding new
@@ -293,14 +362,14 @@ impl VhostUserBackend for VhostUserBlkBackend {
loop { loop {
vring vring
.mut_queue() .mut_queue()
.update_avail_event(self.mem.as_ref().unwrap()); .update_avail_event(thread.mem.as_ref().unwrap());
if !self.process_queue(&mut vring) { if !thread.process_queue(&mut vring) {
break; break;
} }
} }
} else { } else {
// Without EVENT_IDX, a single call is enough. // Without EVENT_IDX, a single call is enough.
self.process_queue(&mut vring); thread.process_queue(&mut vring);
} }
Ok(false) Ok(false)
@@ -310,86 +379,98 @@ impl VhostUserBackend for VhostUserBlkBackend {
} }
fn get_config(&self, _offset: u32, _size: u32) -> Vec<u8> { fn get_config(&self, _offset: u32, _size: u32) -> Vec<u8> {
// self.config is a statically allocated virtio_blk_config self.config.as_slice().to_vec()
let buf = unsafe {
slice::from_raw_parts(
&self.config as *const virtio_blk_config as *const _,
mem::size_of::<virtio_blk_config>(),
)
};
buf.to_vec()
} }
fn exit_event(&self) -> Option<(EventFd, Option<u16>)> { fn set_config(&mut self, offset: u32, data: &[u8]) -> result::Result<(), io::Error> {
Some((self.kill_evt.try_clone().unwrap(), None)) let config_slice = self.config.as_mut_slice();
let data_len = data.len() as u32;
let config_len = config_slice.len() as u32;
if offset + data_len > config_len {
error!("Failed to write config space");
return Err(io::Error::from_raw_os_error(libc::EINVAL));
}
let (_, right) = config_slice.split_at_mut(offset as usize);
right.copy_from_slice(&data[..]);
self.update_writeback();
Ok(())
}
fn exit_event(&self, thread_index: usize) -> Option<(EventFd, Option<u16>)> {
// The exit event is placed after the queue, which is event index 1.
Some((
self.threads[thread_index]
.lock()
.unwrap()
.kill_evt
.try_clone()
.unwrap(),
Some(1),
))
}
fn queues_per_thread(&self) -> Vec<u64> {
self.queues_per_thread.clone()
} }
} }
pub struct VhostUserBlkBackendConfig<'a> { struct VhostUserBlkBackendConfig {
pub image: &'a str, path: String,
pub sock: &'a str, socket: String,
pub num_queues: usize, num_queues: usize,
pub readonly: bool, queue_size: usize,
pub direct: bool, readonly: bool,
pub poll_queue: bool, direct: bool,
poll_queue: bool,
} }
impl<'a> VhostUserBlkBackendConfig<'a> { impl VhostUserBlkBackendConfig {
pub fn parse(backend: &'a str) -> Result<Self> { fn parse(backend: &str) -> Result<Self> {
let params_list: Vec<&str> = backend.split(',').collect(); let mut parser = OptionParser::new();
parser
.add("path")
.add("readonly")
.add("direct")
.add("num_queues")
.add("queue_size")
.add("socket")
.add("poll_queue");
parser.parse(backend).map_err(Error::FailedConfigParse)?;
let mut image: &str = ""; let path = parser.get("path").ok_or(Error::PathParameterMissing)?;
let mut sock: &str = ""; let readonly = parser
let mut num_queues_str: &str = ""; .convert::<Toggle>("readonly")
let mut readonly: bool = false; .map_err(Error::FailedConfigParse)?
let mut direct: bool = false; .unwrap_or(Toggle(false))
let mut poll_queue: bool = true; .0;
let direct = parser
.convert::<Toggle>("direct")
.map_err(Error::FailedConfigParse)?
.unwrap_or(Toggle(false))
.0;
let num_queues = parser
.convert("num_queues")
.map_err(Error::FailedConfigParse)?
.unwrap_or(1);
let socket = parser.get("socket").ok_or(Error::SocketParameterMissing)?;
let poll_queue = parser
.convert::<Toggle>("poll_queue")
.map_err(Error::FailedConfigParse)?
.unwrap_or_else(|| Toggle(true))
.0;
let queue_size = parser
.convert("queue_size")
.map_err(Error::FailedConfigParse)?
.unwrap_or(1024);
for param in params_list.iter() {
if param.starts_with("image=") {
image = &param[6..];
} else if param.starts_with("sock=") {
sock = &param[5..];
} else if param.starts_with("num_queues=") {
num_queues_str = &param[11..];
} else if param.starts_with("readonly=") {
readonly = match param[9..].parse::<bool>() {
Ok(b) => b,
Err(_) => return Err(Error::ParseReadOnlyParam),
}
} else if param.starts_with("direct=") {
direct = match param[7..].parse::<bool>() {
Ok(b) => b,
Err(_) => return Err(Error::ParseDirectParam),
}
} else if param.starts_with("poll_queue=") {
poll_queue = match param[11..].parse::<bool>() {
Ok(b) => b,
Err(_) => return Err(Error::ParsePollQueueParam),
}
}
}
let mut num_queues: usize = 1;
if image.is_empty() {
return Err(Error::ParseImageParam);
}
if sock.is_empty() {
return Err(Error::ParseSockParam);
}
if !num_queues_str.is_empty() {
num_queues = num_queues_str
.parse()
.map_err(Error::ParseBlkNumQueuesParam)?;
}
Ok(VhostUserBlkBackendConfig { Ok(VhostUserBlkBackendConfig {
image, path,
sock, socket,
num_queues, num_queues,
readonly, readonly,
direct, direct,
poll_queue, poll_queue,
queue_size,
}) })
} }
} }
@@ -405,33 +486,26 @@ pub fn start_block_backend(backend_command: &str) {
let blk_backend = Arc::new(RwLock::new( let blk_backend = Arc::new(RwLock::new(
VhostUserBlkBackend::new( VhostUserBlkBackend::new(
backend_config.image.to_string(), backend_config.path,
backend_config.num_queues, backend_config.num_queues,
backend_config.readonly, backend_config.readonly,
backend_config.direct, backend_config.direct,
backend_config.poll_queue, backend_config.poll_queue,
backend_config.queue_size,
) )
.unwrap(), .unwrap(),
)); ));
debug!("blk_backend is created!\n"); debug!("blk_backend is created!\n");
let listener = Listener::new(&backend_config.socket, true).unwrap();
let name = "vhost-user-blk-backend"; let name = "vhost-user-blk-backend";
let mut blk_daemon = VhostUserDaemon::new( let mut blk_daemon = VhostUserDaemon::new(name.to_string(), blk_backend.clone()).unwrap();
name.to_string(),
backend_config.sock.to_string(),
blk_backend.clone(),
)
.unwrap();
debug!("blk_daemon is created!\n"); debug!("blk_daemon is created!\n");
let vring_worker = blk_daemon.get_vring_worker(); if let Err(e) = blk_daemon.start(listener) {
blk_backend
.write()
.unwrap()
.set_vring_worker(Some(vring_worker));
if let Err(e) = blk_daemon.start() {
error!( error!(
"Failed to start daemon for vhost-user-block with error: {:?}\n", "Failed to start daemon for vhost-user-block with error: {:?}\n",
e e
@@ -443,8 +517,9 @@ pub fn start_block_backend(backend_command: &str) {
error!("Error from the main thread: {:?}", e); error!("Error from the main thread: {:?}", e);
} }
let kill_evt = &blk_backend.write().unwrap().kill_evt; for thread in blk_backend.read().unwrap().threads.iter() {
if let Err(e) = kill_evt.write(1) { if let Err(e) = thread.lock().unwrap().kill_evt.write(1) {
error!("Error shutting down worker thread: {:?}", e) error!("Error shutting down worker thread: {:?}", e)
}
} }
} }

View File

@@ -6,8 +6,11 @@ edition = "2018"
[dependencies] [dependencies]
bitflags = "1.1.0" bitflags = "1.1.0"
libc = "0.2.68" libc = "0.2.71"
log = "0.4.8" log = "0.4.8"
vm-memory = "0.1.0" # Match the version in vmm
seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v0.21.1" }
tempdir= "0.3.7"
vm-memory = "0.2.1"
vm-virtio = { path = "../vm-virtio" } vm-virtio = { path = "../vm-virtio" }
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] } vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }

View File

@@ -563,7 +563,7 @@ mod tests {
use DescriptorType::*; use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0); let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap(); let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain( let chain = create_descriptor_chain(
&memory, &memory,
@@ -583,15 +583,15 @@ mod tests {
assert_eq!(reader.bytes_read(), 0); assert_eq!(reader.bytes_read(), 0);
let mut buffer = [0 as u8; 64]; let mut buffer = [0 as u8; 64];
if let Err(_) = reader.read_exact(&mut buffer) { if let Err(e) = reader.read_exact(&mut buffer) {
panic!("read_exact should not fail here"); panic!("read_exact should not fail here: {:?}", e);
} }
assert_eq!(reader.available_bytes(), 42); assert_eq!(reader.available_bytes(), 42);
assert_eq!(reader.bytes_read(), 64); assert_eq!(reader.bytes_read(), 64);
match reader.read(&mut buffer) { match reader.read(&mut buffer) {
Err(_) => panic!("read should not fail here"), Err(e) => panic!("read should not fail here: {:?}", e),
Ok(length) => assert_eq!(length, 42), Ok(length) => assert_eq!(length, 42),
} }
@@ -604,7 +604,7 @@ mod tests {
use DescriptorType::*; use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0); let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap(); let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain( let chain = create_descriptor_chain(
&memory, &memory,
@@ -623,16 +623,16 @@ mod tests {
assert_eq!(writer.available_bytes(), 106); assert_eq!(writer.available_bytes(), 106);
assert_eq!(writer.bytes_written(), 0); assert_eq!(writer.bytes_written(), 0);
let mut buffer = [0 as u8; 64]; let buffer = [0 as u8; 64];
if let Err(_) = writer.write_all(&mut buffer) { if let Err(e) = writer.write_all(&buffer) {
panic!("write_all should not fail here"); panic!("write_all should not fail here: {:?}", e);
} }
assert_eq!(writer.available_bytes(), 42); assert_eq!(writer.available_bytes(), 42);
assert_eq!(writer.bytes_written(), 64); assert_eq!(writer.bytes_written(), 64);
match writer.write(&mut buffer) { match writer.write(&buffer) {
Err(_) => panic!("write should not fail here"), Err(e) => panic!("write should not fail here {:?}", e),
Ok(length) => assert_eq!(length, 42), Ok(length) => assert_eq!(length, 42),
} }
@@ -645,7 +645,7 @@ mod tests {
use DescriptorType::*; use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0); let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap(); let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain( let chain = create_descriptor_chain(
&memory, &memory,
@@ -670,7 +670,7 @@ mod tests {
use DescriptorType::*; use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0); let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap(); let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain( let chain = create_descriptor_chain(
&memory, &memory,
@@ -695,7 +695,7 @@ mod tests {
use DescriptorType::*; use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0); let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap(); let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain( let chain = create_descriptor_chain(
&memory, &memory,
@@ -743,9 +743,9 @@ mod tests {
use DescriptorType::*; use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0); let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap(); let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
let secret: Le32 = 0x12345678.into(); let secret: Le32 = 0x1234_5678.into();
// Create a descriptor chain with memory regions that are properly separated. // Create a descriptor chain with memory regions that are properly separated.
let chain_writer = create_descriptor_chain( let chain_writer = create_descriptor_chain(
@@ -757,8 +757,8 @@ mod tests {
) )
.expect("create_descriptor_chain failed"); .expect("create_descriptor_chain failed");
let mut writer = Writer::new(&memory, chain_writer).expect("failed to create Writer"); let mut writer = Writer::new(&memory, chain_writer).expect("failed to create Writer");
if let Err(_) = writer.write_obj(secret) { if let Err(e) = writer.write_obj(secret) {
panic!("write_obj should not fail here"); panic!("write_obj should not fail here: {:?}", e);
} }
// Now create new descriptor chain pointing to the same memory and try to read it. // Now create new descriptor chain pointing to the same memory and try to read it.
@@ -772,7 +772,7 @@ mod tests {
.expect("create_descriptor_chain failed"); .expect("create_descriptor_chain failed");
let mut reader = Reader::new(&memory, chain_reader).expect("failed to create Reader"); let mut reader = Reader::new(&memory, chain_reader).expect("failed to create Reader");
match reader.read_obj::<Le32>() { match reader.read_obj::<Le32>() {
Err(_) => panic!("read_obj should not fail here"), Err(e) => panic!("read_obj should not fail here: {:?}", e),
Ok(read_secret) => assert_eq!(read_secret, secret), Ok(read_secret) => assert_eq!(read_secret, secret),
} }
} }
@@ -782,7 +782,7 @@ mod tests {
use DescriptorType::*; use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0); let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap(); let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain( let chain = create_descriptor_chain(
&memory, &memory,
@@ -795,8 +795,7 @@ mod tests {
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader"); let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
let mut buf = Vec::with_capacity(1024); let mut buf = vec![0; 1024];
buf.resize(1024, 0);
assert_eq!( assert_eq!(
reader reader
@@ -812,7 +811,7 @@ mod tests {
use DescriptorType::*; use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0); let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap(); let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain( let chain = create_descriptor_chain(
&memory, &memory,
@@ -841,7 +840,7 @@ mod tests {
use DescriptorType::*; use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0); let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap(); let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain( let chain = create_descriptor_chain(
&memory, &memory,
@@ -870,7 +869,7 @@ mod tests {
use DescriptorType::*; use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0); let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap(); let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain( let chain = create_descriptor_chain(
&memory, &memory,
@@ -899,7 +898,7 @@ mod tests {
use DescriptorType::*; use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0); let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap(); let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain( let chain = create_descriptor_chain(
&memory, &memory,
@@ -928,7 +927,7 @@ mod tests {
use DescriptorType::*; use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0); let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap(); let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain( let chain = create_descriptor_chain(
&memory, &memory,
@@ -947,7 +946,7 @@ mod tests {
.expect("create_descriptor_chain failed"); .expect("create_descriptor_chain failed");
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader"); let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
if let Ok(_) = reader.split_at(256) { if reader.split_at(256).is_ok() {
panic!("successfully split Reader with out of bounds offset"); panic!("successfully split Reader with out of bounds offset");
} }
} }
@@ -957,7 +956,7 @@ mod tests {
use DescriptorType::*; use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0); let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap(); let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain( let chain = create_descriptor_chain(
&memory, &memory,
@@ -981,7 +980,7 @@ mod tests {
use DescriptorType::*; use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0); let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap(); let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain( let chain = create_descriptor_chain(
&memory, &memory,

View File

@@ -9,8 +9,6 @@ use std::io;
use std::mem; use std::mem;
use std::time::Duration; use std::time::Duration;
use libc;
use crate::fuse; use crate::fuse;
use super::fs_cache_req_handler::FsCacheReqHandler; use super::fs_cache_req_handler::FsCacheReqHandler;
@@ -682,11 +680,11 @@ pub trait FileSystem {
/// implementation did not return a `Handle` from `open` then the contents of `handle` are /// implementation did not return a `Handle` from `open` then the contents of `handle` are
/// undefined. /// undefined.
/// ///
/// If the `FsOptions::HANDLE_KILLPRIV` feature is not enabled then then the file system is
/// expected to clear the setuid and setgid bits.
///
/// If `delayed_write` is true then it indicates that this is a write for buffered data. /// If `delayed_write` is true then it indicates that this is a write for buffered data.
/// ///
/// If `kill_priv` is true then it indicates that the file system is expected to clear the
/// setuid and setgid bits.
///
/// This method should return exactly the number of bytes requested by the kernel, except in the /// This method should return exactly the number of bytes requested by the kernel, except in the
/// case of error. An exception to this rule is if the file was opened with the "direct I/O" /// case of error. An exception to this rule is if the file was opened with the "direct I/O"
/// option (`libc::O_DIRECT`), in which case the kernel will forward the return code from this /// option (`libc::O_DIRECT`), in which case the kernel will forward the return code from this
@@ -702,6 +700,7 @@ pub trait FileSystem {
offset: u64, offset: u64,
lock_owner: Option<u64>, lock_owner: Option<u64>,
delayed_write: bool, delayed_write: bool,
kill_priv: bool,
flags: u32, flags: u32,
) -> io::Result<usize> { ) -> io::Result<usize> {
Err(io::Error::from_raw_os_error(libc::ENOSYS)) Err(io::Error::from_raw_os_error(libc::ENOSYS))
@@ -1096,6 +1095,22 @@ pub trait FileSystem {
Err(io::Error::from_raw_os_error(libc::ENOSYS)) Err(io::Error::from_raw_os_error(libc::ENOSYS))
} }
#[allow(clippy::too_many_arguments)]
fn copyfilerange(
&self,
ctx: Context,
inode_in: Self::Inode,
handle_in: Self::Handle,
offset_in: u64,
inode_out: Self::Inode,
handle_out: Self::Handle,
offset_out: u64,
len: u64,
flags: u64,
) -> io::Result<usize> {
Err(io::Error::from_raw_os_error(libc::ENOSYS))
}
/// TODO: support this /// TODO: support this
fn getlk(&self) -> io::Result<()> { fn getlk(&self) -> io::Result<()> {
Err(io::Error::from_raw_os_error(libc::ENOSYS)) Err(io::Error::from_raw_os_error(libc::ENOSYS))

View File

@@ -42,7 +42,8 @@ impl FsCacheReqHandler for SlaveFsCacheReq {
VhostUserFSSlaveMsgFlags::MAP_R VhostUserFSSlaveMsgFlags::MAP_R
}; };
self.fs_slave_map(&msg, fd) self.fs_slave_map(&msg, fd)?;
Ok(())
} }
fn unmap(&mut self, requests: Vec<fuse::RemovemappingOne>) -> io::Result<()> { fn unmap(&mut self, requests: Vec<fuse::RemovemappingOne>) -> io::Result<()> {

View File

@@ -5,7 +5,6 @@
use std::mem; use std::mem;
use bitflags::bitflags; use bitflags::bitflags;
use libc;
use vm_memory::ByteValued; use vm_memory::ByteValued;
/// Version number of this interface. /// Version number of this interface.
@@ -55,6 +54,9 @@ const FOPEN_KEEP_CACHE: u32 = 2;
/// The file is not seekable. /// The file is not seekable.
const FOPEN_NONSEEKABLE: u32 = 4; const FOPEN_NONSEEKABLE: u32 = 4;
/// Allow caching this directory.
const FOPEN_CACHE_DIR: u32 = 8;
bitflags! { bitflags! {
/// Options controlling the behavior of files opened by the server in response /// Options controlling the behavior of files opened by the server in response
/// to an open or create request. /// to an open or create request.
@@ -62,6 +64,7 @@ bitflags! {
const DIRECT_IO = FOPEN_DIRECT_IO; const DIRECT_IO = FOPEN_DIRECT_IO;
const KEEP_CACHE = FOPEN_KEEP_CACHE; const KEEP_CACHE = FOPEN_KEEP_CACHE;
const NONSEEKABLE = FOPEN_NONSEEKABLE; const NONSEEKABLE = FOPEN_NONSEEKABLE;
const CACHE_DIR = FOPEN_CACHE_DIR;
} }
} }
@@ -130,6 +133,21 @@ const HANDLE_KILLPRIV: u32 = 524_288;
/// FileSystem supports posix acls. /// FileSystem supports posix acls.
const POSIX_ACL: u32 = 1_048_576; const POSIX_ACL: u32 = 1_048_576;
/// Reading the device after abort returns ECONNABORTED.
const ABORT_ERROR: u32 = 2_097_152;
/// Init_out.max_pages contains the max number of req pages.
const MAX_PAGES: u32 = 4_194_304;
/// Cache READLINK responses
const CACHE_SYMLINKS: u32 = 8_388_608;
/// Kernel supports zero-message opendir
const NO_OPENDIR_SUPPORT: u32 = 16_777_216;
/// Only invalidate cached pages on explicit request
const EXPLICIT_INVAL_DATA: u32 = 33_554_432;
bitflags! { bitflags! {
/// A bitfield passed in as a parameter to and returned from the `init` method of the /// A bitfield passed in as a parameter to and returned from the `init` method of the
/// `FileSystem` trait. /// `FileSystem` trait.
@@ -296,6 +314,43 @@ bitflags! {
/// ///
/// This feature is disabled by default. /// This feature is disabled by default.
const POSIX_ACL = POSIX_ACL; const POSIX_ACL = POSIX_ACL;
/// Indicates that if the connection is gone because of sysfs abort, reading from the device
/// will return -ECONNABORTED.
///
/// This feature is not currently supported.
const ABORT_ERROR = ABORT_ERROR;
/// Indicates support for negotiating the maximum number of pages supported.
///
/// If this feature is enabled, we can tell the kernel the maximum number of pages that we
/// support to transfer in a single request.
///
/// This feature is enabled by default if supported by the kernel.
const MAX_PAGES = MAX_PAGES;
/// Indicates that the kernel supports caching READLINK responses.
///
/// This feature is not currently supported.
const CACHE_SYMLINKS = CACHE_SYMLINKS;
/// Indicates support for zero-message opens. If this flag is set in the `capable` parameter
/// of the `init` trait method, then the file system may return `ENOSYS` from the opendir() handler
/// to indicate success. Further attempts to open directories will be handled in the kernel. (If
/// this flag is not set, returning ENOSYS will be treated as an error and signaled to the
/// caller).
///
/// Setting (or not setting) the field in the `FsOptions` returned from the `init` method
/// has no effect.
const ZERO_MESSAGE_OPENDIR = NO_OPENDIR_SUPPORT;
/// Indicates support for explicit data invalidation. If this feature is enabled, the
/// server is fully responsible for data cache invalidation, and the kernel won't
/// invalidate files data cache on size change and only truncate that cache to new size
/// in case the size decreased.
///
/// This feature is not currently supported.
const EXPLICIT_INVAL_DATA = EXPLICIT_INVAL_DATA;
} }
} }
@@ -317,6 +372,9 @@ pub const WRITE_CACHE: u32 = 1;
/// `lock_owner` field is valid. /// `lock_owner` field is valid.
pub const WRITE_LOCKOWNER: u32 = 2; pub const WRITE_LOCKOWNER: u32 = 2;
/// Kill suid and sgid bits
pub const WRITE_KILL_PRIV: u32 = 4;
// Read flags. // Read flags.
pub const READ_LOCKOWNER: u32 = 2; pub const READ_LOCKOWNER: u32 = 2;
@@ -337,6 +395,9 @@ const IOCTL_32BIT: u32 = 8;
/// Is a directory /// Is a directory
const IOCTL_DIR: u32 = 16; const IOCTL_DIR: u32 = 16;
/// x32 compat ioctl on 64bit machine (64bit time_t)
const IOCTL_COMPAT_X32: u32 = 32;
/// Maximum of in_iovecs + out_iovecs /// Maximum of in_iovecs + out_iovecs
const IOCTL_MAX_IOV: u32 = 256; const IOCTL_MAX_IOV: u32 = 256;
@@ -357,6 +418,9 @@ bitflags! {
/// Is a directory /// Is a directory
const IOCTL_DIR = IOCTL_DIR; const IOCTL_DIR = IOCTL_DIR;
/// x32 compat ioctl on 64bit machine (64bit time_t)
const IOCTL_COMPAT_X32 = IOCTL_COMPAT_X32;
/// Maximum of in_iovecs + out_iovecs /// Maximum of in_iovecs + out_iovecs
const IOCTL_MAX_IOV = IOCTL_MAX_IOV; const IOCTL_MAX_IOV = IOCTL_MAX_IOV;
} }
@@ -835,7 +899,9 @@ pub struct InitOut {
pub congestion_threshold: u16, pub congestion_threshold: u16,
pub max_write: u32, pub max_write: u32,
pub time_gran: u32, pub time_gran: u32,
pub unused: [u32; 9], pub max_pages: u16,
pub padding: u16,
pub unused: [u32; 8],
} }
unsafe impl ByteValued for InitOut {} unsafe impl ByteValued for InitOut {}
@@ -1049,6 +1115,19 @@ pub struct LseekOut {
} }
unsafe impl ByteValued for LseekOut {} unsafe impl ByteValued for LseekOut {}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct CopyfilerangeIn {
pub fh_in: u64,
pub off_in: u64,
pub nodeid_out: u64,
pub fh_out: u64,
pub off_out: u64,
pub len: u64,
pub flags: u64,
}
unsafe impl ByteValued for CopyfilerangeIn {}
bitflags! { bitflags! {
pub struct SetupmappingFlags: u64 { pub struct SetupmappingFlags: u64 {
const WRITE = 0x1; const WRITE = 0x1;

View File

@@ -12,6 +12,8 @@ pub mod fs_cache_req_handler;
pub mod fuse; pub mod fuse;
pub mod multikey; pub mod multikey;
pub mod passthrough; pub mod passthrough;
pub mod sandbox;
pub mod seccomp;
pub mod server; pub mod server;
use std::ffi::FromBytesWithNulError; use std::ffi::FromBytesWithNulError;

View File

@@ -2,6 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
use super::fs_cache_req_handler::FsCacheReqHandler;
use crate::filesystem::{
Context, DirEntry, Entry, FileSystem, FsOptions, GetxattrReply, ListxattrReply, OpenOptions,
SetattrValid, ZeroCopyReader, ZeroCopyWriter,
};
use crate::fuse;
use crate::multikey::MultikeyBTreeMap;
use std::collections::btree_map; use std::collections::btree_map;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
@@ -13,22 +20,12 @@ use std::str::FromStr;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use std::time::Duration; use std::time::Duration;
use libc;
use vm_memory::ByteValued; use vm_memory::ByteValued;
use super::fs_cache_req_handler::FsCacheReqHandler;
use crate::filesystem::{
Context, DirEntry, Entry, FileSystem, FsOptions, GetxattrReply, ListxattrReply, OpenOptions,
SetattrValid, ZeroCopyReader, ZeroCopyWriter,
};
use crate::fuse;
use crate::multikey::MultikeyBTreeMap;
const CURRENT_DIR_CSTR: &[u8] = b".\0"; const CURRENT_DIR_CSTR: &[u8] = b".\0";
const PARENT_DIR_CSTR: &[u8] = b"..\0"; const PARENT_DIR_CSTR: &[u8] = b"..\0";
const EMPTY_CSTR: &[u8] = b"\0"; const EMPTY_CSTR: &[u8] = b"\0";
const PROC_CSTR: &[u8] = b"/proc\0"; const PROC_CSTR: &[u8] = b"/proc/self/fd\0";
type Inode = u64; type Inode = u64;
type Handle = u64; type Handle = u64;
@@ -238,6 +235,13 @@ pub struct Config {
/// ///
/// The default value for this options is `false`. /// The default value for this options is `false`.
pub xattr: bool, pub xattr: bool,
/// Optional file descriptor for /proc/self/fd. Callers can obtain a file descriptor and pass it
/// here, so there's no need to open it in PassthroughFs::new(). This is specially useful for
/// sandboxing.
///
/// The default is `None`.
pub proc_sfd_rawfd: Option<RawFd>,
} }
impl Default for Config { impl Default for Config {
@@ -249,6 +253,7 @@ impl Default for Config {
writeback: false, writeback: false,
root_dir: String::from("/"), root_dir: String::from("/"),
xattr: false, xattr: false,
proc_sfd_rawfd: None,
} }
} }
} }
@@ -271,11 +276,11 @@ pub struct PassthroughFs {
handles: RwLock<BTreeMap<Handle, Arc<HandleData>>>, handles: RwLock<BTreeMap<Handle, Arc<HandleData>>>,
next_handle: AtomicU64, next_handle: AtomicU64,
// File descriptor pointing to the `/proc` directory. This is used to convert an fd from // File descriptor pointing to the `/proc/self/fd` directory. This is used to convert an fd from
// `inodes` into one that can go into `handles`. This is accomplished by reading the // `inodes` into one that can go into `handles`. This is accomplished by reading the
// `self/fd/{}` symlink. We keep an open fd here in case the file system tree that we are meant // `/proc/self/fd/{}` symlink. We keep an open fd here in case the file system tree that we are
// to be serving doesn't have access to `/proc`. // meant to be serving doesn't have access to `/proc/self/fd`.
proc: File, proc_self_fd: File,
// Whether writeback caching is enabled for this directory. This will only be true when // Whether writeback caching is enabled for this directory. This will only be true when
// `cfg.writeback` is true and `init` was called with `FsOptions::WRITEBACK_CACHE`. // `cfg.writeback` is true and `init` was called with `FsOptions::WRITEBACK_CACHE`.
@@ -286,23 +291,29 @@ pub struct PassthroughFs {
impl PassthroughFs { impl PassthroughFs {
pub fn new(cfg: Config) -> io::Result<PassthroughFs> { pub fn new(cfg: Config) -> io::Result<PassthroughFs> {
// Safe because this is a constant value and a valid C string. let fd = if let Some(fd) = cfg.proc_sfd_rawfd {
let proc_cstr = unsafe { CStr::from_bytes_with_nul_unchecked(PROC_CSTR) }; fd
} else {
// Safe because this is a constant value and a valid C string.
let proc_cstr = unsafe { CStr::from_bytes_with_nul_unchecked(PROC_CSTR) };
// Safe because this doesn't modify any memory and we check the return value. // Safe because this doesn't modify any memory and we check the return value.
let fd = unsafe { let fd = unsafe {
libc::openat( libc::openat(
libc::AT_FDCWD, libc::AT_FDCWD,
proc_cstr.as_ptr(), proc_cstr.as_ptr(),
libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC, libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
) )
};
if fd < 0 {
return Err(io::Error::last_os_error());
}
fd
}; };
if fd < 0 {
return Err(io::Error::last_os_error());
}
// Safe because we just opened this fd. // Safe because we just opened this fd or it was provided by our caller.
let proc = unsafe { File::from_raw_fd(fd) }; let proc_self_fd = unsafe { File::from_raw_fd(fd) };
Ok(PassthroughFs { Ok(PassthroughFs {
inodes: RwLock::new(MultikeyBTreeMap::new()), inodes: RwLock::new(MultikeyBTreeMap::new()),
@@ -311,7 +322,7 @@ impl PassthroughFs {
handles: RwLock::new(BTreeMap::new()), handles: RwLock::new(BTreeMap::new()),
next_handle: AtomicU64::new(0), next_handle: AtomicU64::new(0),
proc, proc_self_fd,
writeback: AtomicBool::new(false), writeback: AtomicBool::new(false),
cfg, cfg,
@@ -319,7 +330,7 @@ impl PassthroughFs {
} }
pub fn keep_fds(&self) -> Vec<RawFd> { pub fn keep_fds(&self) -> Vec<RawFd> {
vec![self.proc.as_raw_fd()] vec![self.proc_self_fd.as_raw_fd()]
} }
fn open_inode(&self, inode: Inode, mut flags: i32) -> io::Result<File> { fn open_inode(&self, inode: Inode, mut flags: i32) -> io::Result<File> {
@@ -331,7 +342,7 @@ impl PassthroughFs {
.map(Arc::clone) .map(Arc::clone)
.ok_or_else(ebadf)?; .ok_or_else(ebadf)?;
let pathname = CString::new(format!("self/fd/{}", data.file.as_raw_fd())) let pathname = CString::new(format!("{}", data.file.as_raw_fd()))
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
// When writeback caching is enabled, the kernel may send read requests even if the // When writeback caching is enabled, the kernel may send read requests even if the
@@ -359,7 +370,7 @@ impl PassthroughFs {
// to follow the `/proc/self/fd` symlink to get the file. // to follow the `/proc/self/fd` symlink to get the file.
let fd = unsafe { let fd = unsafe {
libc::openat( libc::openat(
self.proc.as_raw_fd(), self.proc_self_fd.as_raw_fd(),
pathname.as_ptr(), pathname.as_ptr(),
(flags | libc::O_CLOEXEC) & (!libc::O_NOFOLLOW), (flags | libc::O_CLOEXEC) & (!libc::O_NOFOLLOW),
) )
@@ -557,7 +568,13 @@ impl PassthroughFs {
OpenOptions::DIRECT_IO, OpenOptions::DIRECT_IO,
flags & (libc::O_DIRECTORY as u32) == 0, flags & (libc::O_DIRECTORY as u32) == 0,
), ),
CachePolicy::Always => opts |= OpenOptions::KEEP_CACHE, CachePolicy::Always => {
if flags & (libc::O_DIRECTORY as u32) == 0 {
opts |= OpenOptions::KEEP_CACHE;
} else {
opts |= OpenOptions::CACHE_DIR;
}
}
_ => {} _ => {}
}; };
@@ -991,11 +1008,15 @@ impl FileSystem for PassthroughFs {
offset: u64, offset: u64,
_lock_owner: Option<u64>, _lock_owner: Option<u64>,
_delayed_write: bool, _delayed_write: bool,
kill_priv: bool,
_flags: u32, _flags: u32,
) -> io::Result<usize> { ) -> io::Result<usize> {
// We need to change credentials during a write so that the kernel will remove setuid or if kill_priv {
// setgid bits from the file if it was written to by someone other than the owner. // We need to change credentials during a write so that the kernel will remove setuid
let (_uid, _gid) = set_creds(ctx.uid, ctx.gid)?; // or setgid bits from the file if it was written to by someone other than the owner.
let (_uid, _gid) = set_creds(ctx.uid, ctx.gid)?;
}
let data = self let data = self
.handles .handles
.read() .read()
@@ -1055,7 +1076,7 @@ impl FileSystem for PassthroughFs {
let fd = hd.file.write().unwrap().as_raw_fd(); let fd = hd.file.write().unwrap().as_raw_fd();
Data::Handle(hd, fd) Data::Handle(hd, fd)
} else { } else {
let pathname = CString::new(format!("self/fd/{}", inode_data.file.as_raw_fd())) let pathname = CString::new(format!("{}", inode_data.file.as_raw_fd()))
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
Data::ProcPath(pathname) Data::ProcPath(pathname)
}; };
@@ -1066,7 +1087,7 @@ impl FileSystem for PassthroughFs {
match data { match data {
Data::Handle(_, fd) => libc::fchmod(fd, attr.st_mode), Data::Handle(_, fd) => libc::fchmod(fd, attr.st_mode),
Data::ProcPath(ref p) => { Data::ProcPath(ref p) => {
libc::fchmodat(self.proc.as_raw_fd(), p.as_ptr(), attr.st_mode, 0) libc::fchmodat(self.proc_self_fd.as_raw_fd(), p.as_ptr(), attr.st_mode, 0)
} }
} }
}; };
@@ -1152,7 +1173,7 @@ impl FileSystem for PassthroughFs {
let res = match data { let res = match data {
Data::Handle(_, fd) => unsafe { libc::futimens(fd, tvs.as_ptr()) }, Data::Handle(_, fd) => unsafe { libc::futimens(fd, tvs.as_ptr()) },
Data::ProcPath(ref p) => unsafe { Data::ProcPath(ref p) => unsafe {
libc::utimensat(self.proc.as_raw_fd(), p.as_ptr(), tvs.as_ptr(), 0) libc::utimensat(self.proc_self_fd.as_raw_fd(), p.as_ptr(), tvs.as_ptr(), 0)
}, },
}; };
if res < 0 { if res < 0 {
@@ -1644,4 +1665,60 @@ impl FileSystem for PassthroughFs {
Ok(res as u64) Ok(res as u64)
} }
} }
fn copyfilerange(
&self,
_ctx: Context,
inode_in: Inode,
handle_in: Handle,
offset_in: u64,
inode_out: Inode,
handle_out: Handle,
offset_out: u64,
len: u64,
flags: u64,
) -> io::Result<usize> {
let data_in = self
.handles
.read()
.unwrap()
.get(&handle_in)
.filter(|hd| hd.inode == inode_in)
.map(Arc::clone)
.ok_or_else(ebadf)?;
// Take just a read lock as we're not going to alter the file descriptor offset.
let fd_in = data_in.file.read().unwrap().as_raw_fd();
let data_out = self
.handles
.read()
.unwrap()
.get(&handle_out)
.filter(|hd| hd.inode == inode_out)
.map(Arc::clone)
.ok_or_else(ebadf)?;
// Take just a read lock as we're not going to alter the file descriptor offset.
let fd_out = data_out.file.read().unwrap().as_raw_fd();
// Safe because this will only modify `offset_in` and `offset_out` and we check
// the return value.
let res = unsafe {
libc::syscall(
libc::SYS_copy_file_range,
fd_in,
&mut (offset_in as i64) as &mut _ as *mut _,
fd_out,
&mut (offset_out as i64) as &mut _ as *mut _,
len,
flags,
)
};
if res < 0 {
Err(io::Error::last_os_error())
} else {
Ok(res as usize)
}
}
} }

View File

@@ -0,0 +1,292 @@
// Copyright 2020 Red Hat, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::ffi::CString;
use std::os::unix::io::RawFd;
use std::{fmt, io};
use tempdir::TempDir;
#[derive(Debug)]
pub enum Error {
/// Failed to bind mount `/proc/self/fd` into a temporary directory.
BindMountProcSelfFd(io::Error),
/// Failed to bind mount shared directory.
BindMountSharedDir(io::Error),
/// Failed to change to the old root directory.
ChdirOldRoot(io::Error),
/// Failed to change to the new root directory.
ChdirNewRoot(io::Error),
/// Failed to clean the properties of the mount point.
CleanMount(io::Error),
/// Failed to create a temporary directory.
CreateTempDir(io::Error),
/// Call to libc::fork returned an error.
Fork(io::Error),
/// Error bind-mounting a directory.
MountBind(io::Error),
/// Failed to mount old root.
MountOldRoot(io::Error),
/// Error mounting proc.
MountProc(io::Error),
/// Failed to mount new root.
MountNewRoot(io::Error),
/// Error mounting target directory.
MountTarget(io::Error),
/// Failed to open new root.
OpenNewRoot(io::Error),
/// Failed to open old root.
OpenOldRoot(io::Error),
/// Failed to open `/proc/self/fd`.
OpenProcSelfFd(io::Error),
/// Error switching root directory.
PivotRoot(io::Error),
/// Failed to remove temporary directory.
RmdirTempDir(io::Error),
/// Failed to lazily unmount old root.
UmountOldRoot(io::Error),
/// Failed to lazily unmount temporary directory.
UmountTempDir(io::Error),
/// Call to libc::unshare returned an error.
Unshare(io::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "vhost_user_fs_sandbox_error: {:?}", self)
}
}
/// A helper for creating a sandbox for isolating the service.
pub struct Sandbox {
/// The directory that is going to be shared with the VM. The sandbox will be constructed on top
/// of this directory.
shared_dir: String,
/// A file descriptor for `/proc/self/fd` obtained from the sandboxed context.
proc_self_fd: Option<RawFd>,
}
impl Sandbox {
pub fn new(shared_dir: String) -> Self {
Sandbox {
shared_dir,
proc_self_fd: None,
}
}
// Make `self.shared_dir` our root directory, and get an isolated file descriptor for
// `/proc/self/fd`.
//
// This is based on virtiofsd's setup_namespaces() and setup_mounts(), and it's very similar to
// the strategy used in containers. Consists on a careful sequence of mounts and bind-mounts to
// ensure it's not possible to escape the sandbox through `self.shared_dir` nor the file
// descriptor obtained for `/proc/self/fd`.
//
// It's ugly, but it's the only way until Linux implements a proper containerization API.
fn setup_mounts(&mut self) -> Result<(), Error> {
// Ensure our mount changes don't affect the parent mount namespace.
let c_root_dir = CString::new("/").unwrap();
let ret = unsafe {
libc::mount(
std::ptr::null(),
c_root_dir.as_ptr(),
std::ptr::null(),
libc::MS_SLAVE | libc::MS_REC,
std::ptr::null(),
)
};
if ret != 0 {
return Err(Error::CleanMount(std::io::Error::last_os_error()));
}
// Mount `/proc` in this context.
let c_proc_dir = CString::new("/proc").unwrap();
let c_proc_fs = CString::new("proc").unwrap();
let ret = unsafe {
libc::mount(
c_proc_fs.as_ptr(),
c_proc_dir.as_ptr(),
c_proc_fs.as_ptr(),
libc::MS_NODEV | libc::MS_NOEXEC | libc::MS_NOSUID | libc::MS_RELATIME,
std::ptr::null(),
)
};
if ret != 0 {
return Err(Error::MountProc(std::io::Error::last_os_error()));
}
// Bind-mount `/proc/self/fd` info a temporary directory, preventing access to ancestor
// directories.
let c_proc_self_fd = CString::new("/proc/self/fd").unwrap();
let tmp_dir = TempDir::new("vhostuserfs-")
.map_err(|_| Error::CreateTempDir(std::io::Error::last_os_error()))?;
let c_tmp_dir = CString::new(tmp_dir.into_path().to_str().unwrap()).unwrap();
let ret = unsafe {
libc::mount(
c_proc_self_fd.as_ptr(),
c_tmp_dir.as_ptr(),
std::ptr::null(),
libc::MS_BIND,
std::ptr::null(),
)
};
if ret < 0 {
return Err(Error::BindMountProcSelfFd(std::io::Error::last_os_error()));
}
// Obtain a file descriptor for `/proc/self/fd` through the bind-mounted temporary directory.
let proc_self_fd = unsafe { libc::open(c_tmp_dir.as_ptr(), libc::O_PATH) };
if proc_self_fd < 0 {
return Err(Error::OpenProcSelfFd(std::io::Error::last_os_error()));
}
self.proc_self_fd = Some(proc_self_fd);
// Now that we have a file descriptor for `/proc/self/fd`, we no longer need the bind-mount.
// Unmount it and remove the temporary directory.
let ret = unsafe { libc::umount2(c_tmp_dir.as_ptr(), libc::MNT_DETACH) };
if ret < 0 {
return Err(Error::UmountTempDir(std::io::Error::last_os_error()));
}
let ret = unsafe { libc::rmdir(c_tmp_dir.as_ptr()) };
if ret < 0 {
return Err(Error::RmdirTempDir(std::io::Error::last_os_error()));
}
// Bind-mount `self.shared_dir` on itself so we can use as new root on `pivot_root` syscall.
let c_shared_dir = CString::new(self.shared_dir.clone()).unwrap();
let ret = unsafe {
libc::mount(
c_shared_dir.as_ptr(),
c_shared_dir.as_ptr(),
std::ptr::null(),
libc::MS_BIND | libc::MS_REC,
std::ptr::null(),
)
};
if ret < 0 {
return Err(Error::BindMountSharedDir(std::io::Error::last_os_error()));
}
// Get a file descriptor to our old root so we can reference it after switching root.
let oldroot_fd = unsafe {
libc::open(
c_root_dir.as_ptr(),
libc::O_DIRECTORY | libc::O_RDONLY | libc::O_CLOEXEC,
)
};
if oldroot_fd < 0 {
return Err(Error::OpenOldRoot(std::io::Error::last_os_error()));
}
// Get a file descriptor to the new root so we can reference it after switching root.
let newroot_fd = unsafe {
libc::open(
c_shared_dir.as_ptr(),
libc::O_DIRECTORY | libc::O_RDONLY | libc::O_CLOEXEC,
)
};
if newroot_fd < 0 {
return Err(Error::OpenNewRoot(std::io::Error::last_os_error()));
}
// Change to new root directory to prepare for `pivot_root` syscall.
let ret = unsafe { libc::fchdir(newroot_fd) };
if ret < 0 {
return Err(Error::ChdirNewRoot(std::io::Error::last_os_error()));
}
// Call to `pivot_root` using `.` as both new and old root.
let c_current_dir = CString::new(".").unwrap();
let ret = unsafe {
libc::syscall(
libc::SYS_pivot_root,
c_current_dir.as_ptr(),
c_current_dir.as_ptr(),
)
};
if ret < 0 {
return Err(Error::PivotRoot(std::io::Error::last_os_error()));
}
// Change to old root directory to prepare for cleaning up and unmounting it.
let ret = unsafe { libc::fchdir(oldroot_fd) };
if ret < 0 {
return Err(Error::ChdirOldRoot(std::io::Error::last_os_error()));
}
// Clean up old root to avoid mount namespace propagation.
let c_empty = CString::new("").unwrap();
let ret = unsafe {
libc::mount(
c_empty.as_ptr(),
c_current_dir.as_ptr(),
c_empty.as_ptr(),
libc::MS_SLAVE | libc::MS_REC,
std::ptr::null(),
)
};
if ret != 0 {
return Err(Error::CleanMount(std::io::Error::last_os_error()));
}
// Lazily unmount old root.
let ret = unsafe { libc::umount2(c_current_dir.as_ptr(), libc::MNT_DETACH) };
if ret < 0 {
return Err(Error::UmountOldRoot(std::io::Error::last_os_error()));
}
// Change to new root.
let ret = unsafe { libc::fchdir(newroot_fd) };
if ret < 0 {
return Err(Error::ChdirNewRoot(std::io::Error::last_os_error()));
}
// We no longer need these file descriptors, so close them.
unsafe { libc::close(newroot_fd) };
unsafe { libc::close(oldroot_fd) };
Ok(())
}
/// Set up sandbox, fork and jump into it.
///
/// On success, the returned value will be the PID of the child for the parent and `None` for
/// the child itself, with the latter running isolated in `self.shared_dir`.
pub fn enter(&mut self) -> Result<Option<i32>, Error> {
let uid = unsafe { libc::geteuid() };
let flags = if uid == 0 {
libc::CLONE_NEWPID | libc::CLONE_NEWNS | libc::CLONE_NEWNET
} else {
// If running as an unprivileged user, rely on user_namespaces(7) for isolation. The
// main limitation of this strategy is that only the current uid/gid are mapped into
// the new namespace, so most operations on permissions will fail.
libc::CLONE_NEWPID | libc::CLONE_NEWNS | libc::CLONE_NEWNET | libc::CLONE_NEWUSER
};
let ret = unsafe { libc::unshare(flags) };
if ret != 0 {
return Err(Error::Unshare(std::io::Error::last_os_error()));
}
let child = unsafe { libc::fork() };
match child {
0 => {
// This is the child. Request to receive SIGTERM on parent's death.
unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM) };
self.setup_mounts()?;
Ok(None)
}
x if x > 0 => {
// This is the parent.
Ok(Some(child))
}
_ => Err(Error::Fork(std::io::Error::last_os_error())),
}
}
pub fn get_proc_self_fd(&self) -> Option<RawFd> {
self.proc_self_fd
}
}

View File

@@ -0,0 +1,141 @@
// Copyright 2020 Red Hat, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use seccomp::{allow_syscall, BpfProgram, SeccompAction, SeccompFilter};
use std::convert::TryInto;
use std::{convert, fmt};
#[derive(Debug)]
pub enum Error {
/// Cannot create seccomp filter
CreateSeccompFilter(seccomp::SeccompError),
/// Cannot apply seccomp filter
ApplySeccompFilter(seccomp::Error),
}
impl convert::From<seccomp::Error> for Error {
fn from(e: seccomp::Error) -> Self {
Error::ApplySeccompFilter(e)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "vhost_user_fs_seccomp_error: {:?}", self)
}
}
fn vuf_filter(action: SeccompAction) -> Result<SeccompFilter, Error> {
Ok(SeccompFilter::new(
vec![
allow_syscall(libc::SYS_accept4),
allow_syscall(libc::SYS_brk),
allow_syscall(libc::SYS_capget), // For CAP_FSETID
allow_syscall(libc::SYS_capset),
allow_syscall(libc::SYS_clock_gettime),
allow_syscall(libc::SYS_clone),
allow_syscall(libc::SYS_close),
allow_syscall(libc::SYS_copy_file_range),
allow_syscall(libc::SYS_dup),
#[cfg(target_arch = "x86_64")]
allow_syscall(libc::SYS_epoll_create),
allow_syscall(libc::SYS_epoll_create1),
allow_syscall(libc::SYS_epoll_ctl),
allow_syscall(libc::SYS_epoll_pwait),
#[cfg(target_arch = "x86_64")]
allow_syscall(libc::SYS_epoll_wait),
allow_syscall(libc::SYS_eventfd2),
allow_syscall(libc::SYS_exit),
allow_syscall(libc::SYS_exit_group),
allow_syscall(libc::SYS_fallocate),
allow_syscall(libc::SYS_fchdir),
allow_syscall(libc::SYS_fchmodat),
allow_syscall(libc::SYS_fchownat),
allow_syscall(libc::SYS_fcntl),
allow_syscall(libc::SYS_fdatasync),
allow_syscall(libc::SYS_fgetxattr),
allow_syscall(libc::SYS_flistxattr),
allow_syscall(libc::SYS_flock),
allow_syscall(libc::SYS_fremovexattr),
allow_syscall(libc::SYS_fsetxattr),
allow_syscall(libc::SYS_fstat),
#[cfg(target_arch = "x86_64")]
allow_syscall(libc::SYS_fstatfs),
allow_syscall(libc::SYS_fsync),
#[cfg(target_arch = "x86_64")]
allow_syscall(libc::SYS_ftruncate),
allow_syscall(libc::SYS_futex),
#[cfg(target_arch = "x86_64")]
allow_syscall(libc::SYS_getdents),
allow_syscall(libc::SYS_getdents64),
allow_syscall(libc::SYS_getegid),
allow_syscall(libc::SYS_geteuid),
allow_syscall(libc::SYS_getpid),
allow_syscall(libc::SYS_gettid),
allow_syscall(libc::SYS_gettimeofday),
allow_syscall(libc::SYS_getxattr),
allow_syscall(libc::SYS_linkat),
allow_syscall(libc::SYS_listxattr),
allow_syscall(libc::SYS_lseek),
allow_syscall(libc::SYS_madvise),
allow_syscall(libc::SYS_mkdirat),
allow_syscall(libc::SYS_mknodat),
allow_syscall(libc::SYS_mmap),
allow_syscall(libc::SYS_mprotect),
allow_syscall(libc::SYS_mremap),
allow_syscall(libc::SYS_munmap),
allow_syscall(libc::SYS_newfstatat),
#[cfg(target_arch = "x86_64")]
allow_syscall(libc::SYS_open),
allow_syscall(libc::SYS_openat),
allow_syscall(libc::SYS_prctl), // TODO restrict to just PR_SET_NAME?
allow_syscall(libc::SYS_preadv),
allow_syscall(libc::SYS_pread64),
allow_syscall(libc::SYS_pwritev),
allow_syscall(libc::SYS_pwrite64),
allow_syscall(libc::SYS_read),
allow_syscall(libc::SYS_readlinkat),
allow_syscall(libc::SYS_recvmsg),
allow_syscall(libc::SYS_renameat),
allow_syscall(libc::SYS_renameat2),
allow_syscall(libc::SYS_removexattr),
allow_syscall(libc::SYS_rt_sigaction),
allow_syscall(libc::SYS_rt_sigprocmask),
allow_syscall(libc::SYS_rt_sigreturn),
allow_syscall(libc::SYS_sched_getaffinity), // used by thread_pool
allow_syscall(libc::SYS_sendmsg),
allow_syscall(libc::SYS_setresgid),
allow_syscall(libc::SYS_setresuid),
//allow_syscall(libc::SYS_setresgid32), Needed on some platforms,
//allow_syscall(libc::SYS_setresuid32), Needed on some platforms
allow_syscall(libc::SYS_set_robust_list),
allow_syscall(libc::SYS_setxattr),
allow_syscall(libc::SYS_sigaltstack),
allow_syscall(libc::SYS_statx),
allow_syscall(libc::SYS_symlinkat),
#[cfg(target_arch = "x86_64")]
allow_syscall(libc::SYS_time), // Rarely needed, except on static builds
allow_syscall(libc::SYS_tgkill),
allow_syscall(libc::SYS_umask),
#[cfg(target_arch = "x86_64")]
allow_syscall(libc::SYS_unlink),
allow_syscall(libc::SYS_unlinkat),
allow_syscall(libc::SYS_unshare),
allow_syscall(libc::SYS_utimensat),
allow_syscall(libc::SYS_write),
allow_syscall(libc::SYS_writev),
]
.into_iter()
.collect(),
action,
)?)
}
pub fn enable_seccomp(action: SeccompAction) -> Result<(), Error> {
let scfilter = vuf_filter(action)?;
let bpfprog: BpfProgram = scfilter.try_into()?;
SeccompFilter::apply(bpfprog.try_into().unwrap()).unwrap();
Ok(())
}

View File

@@ -2,14 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
use std::ffi::CStr;
use std::fs::File;
use std::io::{self, Read, Write};
use std::mem::size_of;
use libc;
use vm_memory::ByteValued;
use super::fs_cache_req_handler::FsCacheReqHandler; use super::fs_cache_req_handler::FsCacheReqHandler;
use crate::descriptor_utils::{Reader, Writer}; use crate::descriptor_utils::{Reader, Writer};
use crate::filesystem::{ use crate::filesystem::{
@@ -18,8 +10,14 @@ use crate::filesystem::{
}; };
use crate::fuse::*; use crate::fuse::*;
use crate::{Error, Result}; use crate::{Error, Result};
use std::convert::TryInto;
use std::ffi::CStr;
use std::fs::File;
use std::io::{self, Read, Write};
use std::mem::size_of;
use vm_memory::ByteValued;
const MAX_BUFFER_SIZE: u32 = (1 << 20); const MAX_BUFFER_SIZE: u32 = 1 << 20;
const DIRENT_PADDING: [u8; 8] = [0; 8]; const DIRENT_PADDING: [u8; 8] = [0; 8];
struct ZCReader<'a>(Reader<'a>); struct ZCReader<'a>(Reader<'a>);
@@ -124,6 +122,7 @@ impl<F: FileSystem + Sync> Server<F> {
x if x == Opcode::Readdirplus as u32 => self.readdirplus(in_header, r, w), x if x == Opcode::Readdirplus as u32 => self.readdirplus(in_header, r, w),
x if x == Opcode::Rename2 as u32 => self.rename2(in_header, r, w), x if x == Opcode::Rename2 as u32 => self.rename2(in_header, r, w),
x if x == Opcode::Lseek as u32 => self.lseek(in_header, r, w), x if x == Opcode::Lseek as u32 => self.lseek(in_header, r, w),
x if x == Opcode::CopyFileRange as u32 => self.copyfilerange(in_header, r, w),
x if x == Opcode::SetupMapping as u32 => self.setupmapping(in_header, r, w, vu_req), x if x == Opcode::SetupMapping as u32 => self.setupmapping(in_header, r, w, vu_req),
x if x == Opcode::RemoveMapping as u32 => self.removemapping(in_header, r, w, vu_req), x if x == Opcode::RemoveMapping as u32 => self.removemapping(in_header, r, w, vu_req),
_ => reply_error( _ => reply_error(
@@ -634,6 +633,7 @@ impl<F: FileSystem + Sync> Server<F> {
}; };
let delayed_write = write_flags & WRITE_CACHE != 0; let delayed_write = write_flags & WRITE_CACHE != 0;
let kill_priv = write_flags & WRITE_KILL_PRIV != 0;
let data_reader = ZCReader(r); let data_reader = ZCReader(r);
@@ -646,6 +646,7 @@ impl<F: FileSystem + Sync> Server<F> {
offset, offset,
owner, owner,
delayed_write, delayed_write,
kill_priv,
flags, flags,
) { ) {
Ok(count) => { Ok(count) => {
@@ -902,10 +903,14 @@ impl<F: FileSystem + Sync> Server<F> {
| FsOptions::HANDLE_KILLPRIV | FsOptions::HANDLE_KILLPRIV
| FsOptions::ASYNC_DIO | FsOptions::ASYNC_DIO
| FsOptions::HAS_IOCTL_DIR | FsOptions::HAS_IOCTL_DIR
| FsOptions::ATOMIC_O_TRUNC; | FsOptions::ATOMIC_O_TRUNC
| FsOptions::MAX_PAGES;
let capable = FsOptions::from_bits_truncate(flags); let capable = FsOptions::from_bits_truncate(flags);
let page_size: u32 = unsafe { libc::sysconf(libc::_SC_PAGESIZE).try_into().unwrap() };
let max_pages = ((MAX_BUFFER_SIZE - 1) / page_size) + 1;
match self.fs.init(capable) { match self.fs.init(capable) {
Ok(want) => { Ok(want) => {
let enabled = capable & (want | supported); let enabled = capable & (want | supported);
@@ -919,6 +924,7 @@ impl<F: FileSystem + Sync> Server<F> {
congestion_threshold: (::std::u16::MAX / 4) * 3, congestion_threshold: (::std::u16::MAX / 4) * 3,
max_write: MAX_BUFFER_SIZE, max_write: MAX_BUFFER_SIZE,
time_gran: 1, // nanoseconds time_gran: 1, // nanoseconds
max_pages: max_pages.try_into().unwrap(),
..Default::default() ..Default::default()
}; };
@@ -1261,6 +1267,41 @@ impl<F: FileSystem + Sync> Server<F> {
Err(e) => reply_error(e, in_header.unique, w), Err(e) => reply_error(e, in_header.unique, w),
} }
} }
fn copyfilerange(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
let CopyfilerangeIn {
fh_in,
off_in,
nodeid_out,
fh_out,
off_out,
len,
flags,
..
} = r.read_obj().map_err(Error::DecodeMessage)?;
match self.fs.copyfilerange(
Context::from(in_header),
in_header.nodeid.into(),
fh_in.into(),
off_in,
nodeid_out.into(),
fh_out.into(),
off_out,
len,
flags,
) {
Ok(count) => {
let out = WriteOut {
size: count as u32,
..Default::default()
};
reply_ok(Some(out), None, in_header.unique, w)
}
Err(e) => reply_error(e, in_header.unique, w),
}
}
} }
fn reply_ok<T: ByteValued>( fn reply_ok<T: ByteValued>(

View File

@@ -7,12 +7,13 @@ edition = "2018"
[dependencies] [dependencies]
bitflags = "1.1.0" bitflags = "1.1.0"
epoll = ">=4.0.1" epoll = ">=4.0.1"
libc = "0.2.68" libc = "0.2.71"
log = "0.4.8" log = "0.4.8"
net_util = { path = "../net_util" } net_util = { path = "../net_util" }
vhost_user_backend = { path = "../vhost_user_backend" } vhost_user_backend = { path = "../vhost_user_backend" }
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] } vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
virtio-bindings = "0.1.0" virtio-bindings = "0.1.0"
vm-memory = "0.1.0" vm-memory = "0.2.1"
vm-virtio = { path = "../vm-virtio" } vm-virtio = { path = "../vm-virtio" }
vmm = { path = "../vmm" }
vmm-sys-util = ">=0.3.1" vmm-sys-util = ">=0.3.1"

View File

@@ -11,27 +11,27 @@ extern crate net_util;
extern crate vhost_rs; extern crate vhost_rs;
extern crate vhost_user_backend; extern crate vhost_user_backend;
extern crate vm_virtio; extern crate vm_virtio;
extern crate vmm;
use epoll; use libc::{self, EFD_NONBLOCK};
use libc::{self, EAGAIN, EFD_NONBLOCK};
use log::*; use log::*;
use net_util::Tap; use net_util::{MacAddr, Tap};
use std::convert::TryFrom;
use std::fmt; use std::fmt;
use std::io::Read;
use std::io::{self}; use std::io::{self};
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::process; use std::process;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, Mutex, RwLock};
use std::vec::Vec; use std::vec::Vec;
use vhost_rs::vhost_user::message::*; use vhost_rs::vhost_user::message::*;
use vhost_rs::vhost_user::Error as VhostUserError; use vhost_rs::vhost_user::{Error as VhostUserError, Listener};
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring, VringWorker}; use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring, VringWorker};
use virtio_bindings::bindings::virtio_net::*; use virtio_bindings::bindings::virtio_net::*;
use vm_memory::GuestMemoryMmap; use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap};
use vm_virtio::net_util::{open_tap, RxVirtio, TxVirtio}; use vm_virtio::net_util::{open_tap, RxVirtio, TxVirtio};
use vm_virtio::Queue; use vm_virtio::NetQueuePair;
use vmm::config::{OptionParser, OptionParserError};
use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::eventfd::EventFd;
pub type VhostUserResult<T> = std::result::Result<T, VhostUserError>; pub type VhostUserResult<T> = std::result::Result<T, VhostUserError>;
@@ -52,8 +52,10 @@ pub enum Error {
EpollCreateFd, EpollCreateFd,
/// Failed to read Tap. /// Failed to read Tap.
FailedReadTap, FailedReadTap,
/// Failed to parse configuration string
FailedConfigParse(OptionParserError),
/// Failed to signal used queue. /// Failed to signal used queue.
FailedSignalingUsedQueue, FailedSignalingUsedQueue(io::Error),
/// Failed to handle event other than input event. /// Failed to handle event other than input event.
HandleEventNotEpollIn, HandleEventNotEpollIn,
/// Failed to handle unknown event. /// Failed to handle unknown event.
@@ -64,20 +66,18 @@ pub enum Error {
NoVringCallFdNotify, NoVringCallFdNotify,
/// No memory configured. /// No memory configured.
NoMemoryConfigured, NoMemoryConfigured,
/// Failed to parse sock parameter.
ParseSockParam,
/// Failed to parse ip parameter.
ParseIpParam(std::net::AddrParseError),
/// Failed to parse mask parameter.
ParseMaskParam(std::net::AddrParseError),
/// Failed to parse queue number.
ParseQueueNumParam(std::num::ParseIntError),
/// Failed to parse queue size.
ParseQueueSizeParam(std::num::ParseIntError),
/// Open tap device failed. /// Open tap device failed.
OpenTap(vm_virtio::net_util::Error), OpenTap(vm_virtio::net_util::Error),
/// No socket provided
SocketParameterMissing,
/// Underlying QueuePair error
NetQueuePair(vm_virtio::Error),
} }
pub const SYNTAX: &str = "vhost-user-net backend parameters \
\"ip=<ip_addr>,mask=<net_mask>,socket=<socket_path>,\
num_queues=<number_of_queues>,queue_size=<size_of_each_queue>,tap=<if_name>\"";
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "vhost_user_net_error: {:?}", self) write!(f, "vhost_user_net_error: {:?}", self)
@@ -92,163 +92,74 @@ impl std::convert::From<Error> for std::io::Error {
} }
} }
pub struct VhostUserNetBackend { struct VhostUserNetThread {
mem: Option<GuestMemoryMmap>, net: NetQueuePair,
vring_worker: Option<Arc<VringWorker>>, vring_worker: Option<Arc<VringWorker>>,
kill_evt: EventFd, kill_evt: EventFd,
taps: Vec<(Tap, usize)>,
rxs: Vec<RxVirtio>,
txs: Vec<TxVirtio>,
rx_tap_listenings: Vec<bool>,
num_queues: usize,
queue_size: u16,
} }
impl VhostUserNetBackend { impl VhostUserNetThread {
/// Create a new virtio network device with the given TAP interface. /// Create a new virtio network device with the given TAP interface.
pub fn new_with_tap(taps: Vec<Tap>, num_queues: usize, queue_size: u16) -> Result<Self> { fn new(tap: Tap) -> Result<Self> {
let mut taps_v: Vec<(Tap, usize)> = Vec::new(); Ok(VhostUserNetThread {
for (i, tap) in taps.iter().enumerate() {
taps_v.push((tap.clone(), num_queues + i));
}
let mut rxs: Vec<RxVirtio> = Vec::new();
let mut txs: Vec<TxVirtio> = Vec::new();
let mut rx_tap_listenings: Vec<bool> = Vec::new();
for _ in 0..taps.len() {
let rx = RxVirtio::new();
rxs.push(rx);
let tx = TxVirtio::new();
txs.push(tx);
rx_tap_listenings.push(false);
}
Ok(VhostUserNetBackend {
mem: None,
vring_worker: None, vring_worker: None,
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?, kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?,
taps: taps_v, net: NetQueuePair {
rxs, mem: None,
txs, tap,
rx_tap_listenings, rx: RxVirtio::new(),
num_queues, tx: TxVirtio::new(),
queue_size, rx_tap_listening: false,
epoll_fd: None,
},
}) })
} }
/// Create a new virtio network device with the given IP address and pub fn set_vring_worker(&mut self, vring_worker: Option<Arc<VringWorker>>) {
/// netmask. self.net.epoll_fd = Some(vring_worker.as_ref().unwrap().as_raw_fd());
pub fn new( self.vring_worker = vring_worker;
}
}
pub struct VhostUserNetBackend {
threads: Vec<Mutex<VhostUserNetThread>>,
num_queues: usize,
queue_size: u16,
queues_per_thread: Vec<u64>,
}
impl VhostUserNetBackend {
fn new(
ip_addr: Ipv4Addr, ip_addr: Ipv4Addr,
host_mac: MacAddr,
netmask: Ipv4Addr, netmask: Ipv4Addr,
num_queues: usize, num_queues: usize,
queue_size: u16, queue_size: u16,
ifname: Option<&str>, ifname: Option<&str>,
) -> Result<Self> { ) -> Result<Self> {
let taps = open_tap(ifname, Some(ip_addr), Some(netmask), num_queues / 2) let mut taps = open_tap(
.map_err(Error::OpenTap)?; ifname,
Some(ip_addr),
Some(netmask),
&mut Some(host_mac),
num_queues / 2,
)
.map_err(Error::OpenTap)?;
Self::new_with_tap(taps, num_queues, queue_size) let mut queues_per_thread = Vec::new();
} let mut threads = Vec::new();
for (i, tap) in taps.drain(..).enumerate() {
// Copies a single frame from `self.rx.frame_buf` into the guest. Returns true let thread = Mutex::new(VhostUserNetThread::new(tap)?);
// if a buffer was used, and false if the frame must be deferred until a buffer threads.push(thread);
// is made available by the driver. queues_per_thread.push(0b11 << (i * 2));
fn rx_single_frame(&mut self, mut queue: &mut Queue, index: usize) -> Result<bool> {
let mem = self.mem.as_ref().ok_or(Error::NoMemoryConfigured)?;
let next_desc = queue.iter(&mem).next();
if next_desc.is_none() {
// Queue has no available descriptors
if self.rx_tap_listenings[index] {
self.vring_worker
.as_ref()
.unwrap()
.unregister_listener(
self.taps[index].0.as_raw_fd(),
epoll::Events::EPOLLIN,
u64::try_from(self.taps[index].1).unwrap(),
)
.unwrap();
self.rx_tap_listenings[index] = false;
}
return Ok(false);
} }
let write_complete = self.rxs[index].process_desc_chain(&mem, next_desc, &mut queue); Ok(VhostUserNetBackend {
threads,
Ok(write_complete) num_queues,
} queue_size,
queues_per_thread,
fn process_rx(&mut self, vring: &mut Vring, index: usize) -> Result<()> { })
// Read as many frames as possible.
loop {
match self.read_tap(index) {
Ok(count) => {
self.rxs[index].bytes_read = count;
if !self.rx_single_frame(&mut vring.mut_queue(), index)? {
self.rxs[index].deferred_frame = true;
break;
}
}
Err(e) => {
// The tap device is non-blocking, so any error aside from EAGAIN is
// unexpected.
match e.raw_os_error() {
Some(err) if err == EAGAIN => (),
_ => {
error!("Failed to read tap: {:?}", e);
return Err(Error::FailedReadTap);
}
};
break;
}
}
}
if self.rxs[index].deferred_irqs {
self.rxs[index].deferred_irqs = false;
vring.signal_used_queue().unwrap();
Ok(())
} else {
Ok(())
}
}
fn resume_rx(&mut self, vring: &mut Vring, index: usize) -> Result<()> {
if self.rxs[index].deferred_frame {
if self.rx_single_frame(&mut vring.mut_queue(), index)? {
self.rxs[index].deferred_frame = false;
// process_rx() was interrupted possibly before consuming all
// packets in the tap; try continuing now.
self.process_rx(vring, index)
} else if self.rxs[index].deferred_irqs {
self.rxs[index].deferred_irqs = false;
vring.signal_used_queue().unwrap();
Ok(())
} else {
Ok(())
}
} else {
Ok(())
}
}
fn process_tx(&mut self, mut queue: &mut Queue, index: usize) -> Result<()> {
let mem = self.mem.as_ref().ok_or(Error::NoMemoryConfigured)?;
self.txs[index].process_desc_chain(&mem, &mut self.taps[index].0, &mut queue);
Ok(())
}
fn read_tap(&mut self, index: usize) -> io::Result<usize> {
self.taps[index].0.read(&mut self.rxs[index].frame_buf)
}
pub fn set_vring_worker(&mut self, vring_worker: Option<Arc<VringWorker>>) {
self.vring_worker = vring_worker;
} }
} }
@@ -269,6 +180,7 @@ impl VhostUserBackend for VhostUserNetBackend {
| 1 << VIRTIO_NET_F_HOST_TSO4 | 1 << VIRTIO_NET_F_HOST_TSO4
| 1 << VIRTIO_NET_F_HOST_UFO | 1 << VIRTIO_NET_F_HOST_UFO
| 1 << VIRTIO_F_VERSION_1 | 1 << VIRTIO_F_VERSION_1
| 1 << VIRTIO_RING_F_EVENT_IDX
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
} }
@@ -279,59 +191,59 @@ impl VhostUserBackend for VhostUserNetBackend {
fn set_event_idx(&mut self, _enabled: bool) {} fn set_event_idx(&mut self, _enabled: bool) {}
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> { fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
self.mem = Some(mem); for thread in self.threads.iter() {
thread.lock().unwrap().net.mem = Some(GuestMemoryAtomic::new(mem.clone()));
}
Ok(()) Ok(())
} }
fn handle_event( fn handle_event(
&mut self, &self,
device_event: u16, device_event: u16,
evset: epoll::Events, evset: epoll::Events,
vrings: &[Arc<RwLock<Vring>>], vrings: &[Arc<RwLock<Vring>>],
thread_id: usize,
) -> VhostUserBackendResult<bool> { ) -> VhostUserBackendResult<bool> {
if evset != epoll::Events::EPOLLIN { if evset != epoll::Events::EPOLLIN {
return Err(Error::HandleEventNotEpollIn.into()); return Err(Error::HandleEventNotEpollIn.into());
} }
let tap_start_index = self.num_queues as u16; let mut thread = self.threads[thread_id].lock().unwrap();
let tap_end_index = (self.num_queues + self.num_queues / 2 - 1) as u16;
match device_event { match device_event {
x if ((x < self.num_queues as u16) && (x % 2 == 0)) => { 0 => {
let index = (x / 2) as usize; let mut vring = vrings[0].write().unwrap();
let mut vring = vrings[x as usize].write().unwrap(); if thread
self.resume_rx(&mut vring, index)?; .net
.resume_rx(&mut vring.mut_queue())
if !self.rx_tap_listenings[index] { .map_err(Error::NetQueuePair)?
self.vring_worker.as_ref().unwrap().register_listener( {
self.taps[index].0.as_raw_fd(), vring
epoll::Events::EPOLLIN, .signal_used_queue()
u64::try_from(self.taps[index].1).unwrap(), .map_err(Error::FailedSignalingUsedQueue)?
)?;
self.rx_tap_listenings[index] = true;
} }
} }
x if ((x < self.num_queues as u16) && (x % 2 != 0)) => { 1 => {
let index = ((x - 1) / 2) as usize; let mut vring = vrings[1].write().unwrap();
let mut vring = vrings[x as usize].write().unwrap(); if thread
self.process_tx(&mut vring.mut_queue(), index)?; .net
} .process_tx(&mut vring.mut_queue())
x if x >= tap_start_index && x <= tap_end_index => { .map_err(Error::NetQueuePair)?
let index = x as usize - self.num_queues;
let mut vring = vrings[2 * index].write().unwrap();
if self.rxs[index].deferred_frame
// Process a deferred frame first if available. Don't read from tap again
// until we manage to receive this deferred frame.
{ {
if self.rx_single_frame(&mut vring.mut_queue(), index)? { vring
self.rxs[index].deferred_frame = false; .signal_used_queue()
self.process_rx(&mut vring, index)?; .map_err(Error::FailedSignalingUsedQueue)?
} else if self.rxs[index].deferred_irqs { }
self.rxs[index].deferred_irqs = false; }
vring.signal_used_queue()?; 2 => {
} let mut vring = vrings[0].write().unwrap();
} else { if thread
self.process_rx(&mut vring, index)?; .net
.process_rx_tap(&mut vring.mut_queue())
.map_err(Error::NetQueuePair)?
{
vring
.signal_used_queue()
.map_err(Error::FailedSignalingUsedQueue)?
} }
} }
_ => return Err(Error::HandleEventUnknownEvent.into()), _ => return Err(Error::HandleEventUnknownEvent.into()),
@@ -340,78 +252,78 @@ impl VhostUserBackend for VhostUserNetBackend {
Ok(false) Ok(false)
} }
fn exit_event(&self) -> Option<(EventFd, Option<u16>)> { fn exit_event(&self, thread_index: usize) -> Option<(EventFd, Option<u16>)> {
let tap_end_index = (self.num_queues + self.num_queues / 2 - 1) as u16; // The exit event is placed after the queues and the tap event, which
let kill_index = tap_end_index + 1; // is event index 3.
Some((self.kill_evt.try_clone().unwrap(), Some(kill_index))) Some((
self.threads[thread_index]
.lock()
.unwrap()
.kill_evt
.try_clone()
.unwrap(),
Some(3),
))
}
fn queues_per_thread(&self) -> Vec<u64> {
self.queues_per_thread.clone()
} }
} }
pub struct VhostUserNetBackendConfig<'a> { pub struct VhostUserNetBackendConfig {
pub ip: Ipv4Addr, pub ip: Ipv4Addr,
pub host_mac: MacAddr,
pub mask: Ipv4Addr, pub mask: Ipv4Addr,
pub sock: &'a str, pub socket: String,
pub num_queues: usize, pub num_queues: usize,
pub queue_size: u16, pub queue_size: u16,
pub tap: Option<&'a str>, pub tap: Option<String>,
} }
impl<'a> VhostUserNetBackendConfig<'a> { impl VhostUserNetBackendConfig {
pub fn parse(backend: &'a str) -> Result<Self> { pub fn parse(backend: &str) -> Result<Self> {
let params_list: Vec<&str> = backend.split(',').collect(); let mut parser = OptionParser::new();
let mut ip_str: &str = ""; parser
let mut mask_str: &str = ""; .add("tap")
let mut sock: &str = ""; .add("ip")
let mut num_queues_str: &str = ""; .add("host_mac")
let mut queue_size_str: &str = ""; .add("mask")
let mut tap_str: &str = ""; .add("queue_size")
.add("num_queues")
.add("socket");
for param in params_list.iter() { parser.parse(backend).map_err(Error::FailedConfigParse)?;
if param.starts_with("ip=") {
ip_str = &param[3..];
} else if param.starts_with("mask=") {
mask_str = &param[5..];
} else if param.starts_with("sock=") {
sock = &param[5..];
} else if param.starts_with("num_queues=") {
num_queues_str = &param[11..];
} else if param.starts_with("queue_size=") {
queue_size_str = &param[11..];
} else if param.starts_with("tap=") {
tap_str = &param[4..];
}
}
let mut ip: Ipv4Addr = Ipv4Addr::new(192, 168, 100, 1); let tap = parser.get("tap");
let mut mask: Ipv4Addr = Ipv4Addr::new(255, 255, 255, 0); let ip = parser
let mut num_queues: usize = 2; .convert("ip")
let mut queue_size: u16 = 256; .map_err(Error::FailedConfigParse)?
let mut tap: Option<&str> = None; .unwrap_or_else(|| Ipv4Addr::new(192, 168, 100, 1));
let host_mac = parser
if sock.is_empty() { .convert("host_mac")
return Err(Error::ParseSockParam); .map_err(Error::FailedConfigParse)?
} .unwrap_or_else(MacAddr::local_random);
if !ip_str.is_empty() { let mask = parser
ip = ip_str.parse().map_err(Error::ParseIpParam)?; .convert("mask")
} .map_err(Error::FailedConfigParse)?
if !mask_str.is_empty() { .unwrap_or_else(|| Ipv4Addr::new(255, 255, 255, 0));
mask = mask_str.parse().map_err(Error::ParseMaskParam)?; let queue_size = parser
} .convert("queue_size")
if !num_queues_str.is_empty() { .map_err(Error::FailedConfigParse)?
num_queues = num_queues_str.parse().map_err(Error::ParseQueueNumParam)?; .unwrap_or(256);
} let num_queues = parser
if !queue_size_str.is_empty() { .convert("num_queues")
queue_size = queue_size_str.parse().map_err(Error::ParseQueueSizeParam)?; .map_err(Error::FailedConfigParse)?
} .unwrap_or(2);
if !tap_str.is_empty() { let socket = parser.get("socket").ok_or(Error::SocketParameterMissing)?;
tap = Some(tap_str);
}
Ok(VhostUserNetBackendConfig { Ok(VhostUserNetBackendConfig {
ip, ip,
host_mac,
mask, mask,
sock, socket,
num_queues, num_queues,
queue_size, queue_size,
tap, tap,
@@ -423,38 +335,50 @@ pub fn start_net_backend(backend_command: &str) {
let backend_config = match VhostUserNetBackendConfig::parse(backend_command) { let backend_config = match VhostUserNetBackendConfig::parse(backend_command) {
Ok(config) => config, Ok(config) => config,
Err(e) => { Err(e) => {
println!("Failed parsing parameters {:?}", e); eprintln!("Failed parsing parameters {:?}", e);
process::exit(1); process::exit(1);
} }
}; };
let tap = if let Some(tap) = backend_config.tap.as_ref() {
Some(tap.as_str())
} else {
None
};
let net_backend = Arc::new(RwLock::new( let net_backend = Arc::new(RwLock::new(
VhostUserNetBackend::new( VhostUserNetBackend::new(
backend_config.ip, backend_config.ip,
backend_config.host_mac,
backend_config.mask, backend_config.mask,
backend_config.num_queues, backend_config.num_queues,
backend_config.queue_size, backend_config.queue_size,
backend_config.tap, tap,
) )
.unwrap(), .unwrap(),
)); ));
let mut net_daemon = VhostUserDaemon::new( let listener = Listener::new(&backend_config.socket, true).unwrap();
"vhost-user-net-backend".to_string(),
backend_config.sock.to_string(),
net_backend.clone(),
)
.unwrap();
let vring_worker = net_daemon.get_vring_worker(); let mut net_daemon =
VhostUserDaemon::new("vhost-user-net-backend".to_string(), net_backend.clone()).unwrap();
net_backend let mut vring_workers = net_daemon.get_vring_workers();
.write()
.unwrap()
.set_vring_worker(Some(vring_worker));
if let Err(e) = net_daemon.start() { if vring_workers.len() != net_backend.read().unwrap().threads.len() {
println!( error!("Number of vring workers must be identical to the number of backend threads");
process::exit(1);
}
for thread in net_backend.read().unwrap().threads.iter() {
thread
.lock()
.unwrap()
.set_vring_worker(Some(vring_workers.remove(0)));
}
if let Err(e) = net_daemon.start(listener) {
error!(
"failed to start daemon for vhost-user-net with error: {:?}", "failed to start daemon for vhost-user-net with error: {:?}",
e e
); );
@@ -465,8 +389,9 @@ pub fn start_net_backend(backend_command: &str) {
error!("Error from the main thread: {:?}", e); error!("Error from the main thread: {:?}", e);
} }
let kill_evt = &net_backend.write().unwrap().kill_evt; for thread in net_backend.read().unwrap().threads.iter() {
if let Err(e) = kill_evt.write(1) { if let Err(e) = thread.lock().unwrap().kill_evt.write(1) {
error!("Error shutting down worker thread: {:?}", e) error!("Error shutting down worker thread: {:?}", e)
}
} }
} }

View File

@@ -5,5 +5,6 @@ authors = ["The Chromium OS Authors"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
libc = "0.2.68" libc = "0.2.71"
vm-memory = "0.1.0" vm-memory = "0.2.1"
arch = { path = "../arch" }

0
vm-allocator/src/address.rs Executable file → Normal file
View File

View File

@@ -2,6 +2,9 @@
// //
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
#[cfg(target_arch = "aarch64")]
use arch;
#[cfg(target_arch = "x86_64")]
use std::collections::btree_map::BTreeMap; use std::collections::btree_map::BTreeMap;
use std::result; use std::result;
@@ -13,12 +16,14 @@ pub enum Error {
pub type Result<T> = result::Result<T, Error>; pub type Result<T> = result::Result<T, Error>;
/// GsiApic /// GsiApic
#[cfg(target_arch = "x86_64")]
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct GsiApic { pub struct GsiApic {
base: u32, base: u32,
irqs: u32, irqs: u32,
} }
#[cfg(target_arch = "x86_64")]
impl GsiApic { impl GsiApic {
/// New GSI APIC /// New GSI APIC
pub fn new(base: u32, irqs: u32) -> Self { pub fn new(base: u32, irqs: u32) -> Self {
@@ -28,12 +33,14 @@ impl GsiApic {
/// GsiAllocator /// GsiAllocator
pub struct GsiAllocator { pub struct GsiAllocator {
#[cfg(target_arch = "x86_64")]
apics: BTreeMap<u32, u32>, apics: BTreeMap<u32, u32>,
next_irq: u32, next_irq: u32,
next_gsi: u32, next_gsi: u32,
} }
impl GsiAllocator { impl GsiAllocator {
#[cfg(target_arch = "x86_64")]
/// New GSI allocator /// New GSI allocator
pub fn new(apics: Vec<GsiApic>) -> Self { pub fn new(apics: Vec<GsiApic>) -> Self {
let mut allocator = GsiAllocator { let mut allocator = GsiAllocator {
@@ -57,13 +64,23 @@ impl GsiAllocator {
allocator allocator
} }
/// Allocate a GSI #[cfg(target_arch = "aarch64")]
pub fn allocate_gsi(&mut self) -> Result<u32> { /// New GSI allocator
self.next_gsi = self.next_gsi.checked_add(1).ok_or(Error::Overflow)?; pub fn new() -> Self {
GsiAllocator {
Ok(self.next_gsi - 1) next_irq: arch::IRQ_BASE,
next_gsi: arch::IRQ_BASE,
}
} }
/// Allocate a GSI
pub fn allocate_gsi(&mut self) -> Result<u32> {
let gsi = self.next_gsi;
self.next_gsi = self.next_gsi.checked_add(1).ok_or(Error::Overflow)?;
Ok(gsi)
}
#[cfg(target_arch = "x86_64")]
/// Allocate an IRQ /// Allocate an IRQ
pub fn allocate_irq(&mut self) -> Result<u32> { pub fn allocate_irq(&mut self) -> Result<u32> {
let mut irq: u32 = 0; let mut irq: u32 = 0;
@@ -81,4 +98,12 @@ impl GsiAllocator {
Ok(irq) Ok(irq)
} }
#[cfg(target_arch = "aarch64")]
/// Allocate an IRQ
pub fn allocate_irq(&mut self) -> Result<u32> {
let irq = self.next_irq;
self.next_irq = self.next_irq.checked_add(1).ok_or(Error::Overflow)?;
Ok(irq)
}
} }

View File

@@ -18,5 +18,7 @@ mod gsi;
mod system; mod system;
pub use crate::address::AddressAllocator; pub use crate::address::AddressAllocator;
pub use crate::gsi::{GsiAllocator, GsiApic}; pub use crate::gsi::GsiAllocator;
#[cfg(target_arch = "x86_64")]
pub use crate::gsi::GsiApic;
pub use crate::system::SystemAllocator; pub use crate::system::SystemAllocator;

26
vm-allocator/src/system.rs Executable file → Normal file
View File

@@ -10,7 +10,9 @@
use vm_memory::{GuestAddress, GuestUsize}; use vm_memory::{GuestAddress, GuestUsize};
use crate::address::AddressAllocator; use crate::address::AddressAllocator;
use crate::gsi::{GsiAllocator, GsiApic}; use crate::gsi::GsiAllocator;
#[cfg(target_arch = "x86_64")]
use crate::gsi::GsiApic;
use libc::{sysconf, _SC_PAGESIZE}; use libc::{sysconf, _SC_PAGESIZE};
@@ -39,6 +41,7 @@ fn pagesize() -> usize {
/// ///
/// ``` /// ```
pub struct SystemAllocator { pub struct SystemAllocator {
#[cfg(target_arch = "x86_64")]
io_address_space: AddressAllocator, io_address_space: AddressAllocator,
mmio_address_space: AddressAllocator, mmio_address_space: AddressAllocator,
mmio_hole_address_space: AddressAllocator, mmio_hole_address_space: AddressAllocator,
@@ -49,25 +52,32 @@ impl SystemAllocator {
/// Creates a new `SystemAllocator` for managing addresses and irq numvers. /// Creates a new `SystemAllocator` for managing addresses and irq numvers.
/// Can return `None` if `base` + `size` overflows a u64 /// Can return `None` if `base` + `size` overflows a u64
/// ///
/// * `io_base` - The starting address of IO memory. /// * `io_base` - (X86) The starting address of IO memory.
/// * `io_size` - The size of IO memory. /// * `io_size` - (X86) The size of IO memory.
/// * `mmio_base` - The starting address of MMIO memory. /// * `mmio_base` - The starting address of MMIO memory.
/// * `mmio_size` - The size of MMIO memory. /// * `mmio_size` - The size of MMIO memory.
/// * `first_irq` - The first irq number to give out. /// * `mmio_hole_base` - The starting address of MMIO memory in 32-bit address space.
/// * `mmio_hole_size` - The size of MMIO memory in 32-bit address space.
/// * `apics` - (X86) Vector of APIC's.
///
pub fn new( pub fn new(
io_base: GuestAddress, #[cfg(target_arch = "x86_64")] io_base: GuestAddress,
io_size: GuestUsize, #[cfg(target_arch = "x86_64")] io_size: GuestUsize,
mmio_base: GuestAddress, mmio_base: GuestAddress,
mmio_size: GuestUsize, mmio_size: GuestUsize,
mmio_hole_base: GuestAddress, mmio_hole_base: GuestAddress,
mmio_hole_size: GuestUsize, mmio_hole_size: GuestUsize,
apics: Vec<GsiApic>, #[cfg(target_arch = "x86_64")] apics: Vec<GsiApic>,
) -> Option<Self> { ) -> Option<Self> {
Some(SystemAllocator { Some(SystemAllocator {
#[cfg(target_arch = "x86_64")]
io_address_space: AddressAllocator::new(io_base, io_size)?, io_address_space: AddressAllocator::new(io_base, io_size)?,
mmio_address_space: AddressAllocator::new(mmio_base, mmio_size)?, mmio_address_space: AddressAllocator::new(mmio_base, mmio_size)?,
mmio_hole_address_space: AddressAllocator::new(mmio_hole_base, mmio_hole_size)?, mmio_hole_address_space: AddressAllocator::new(mmio_hole_base, mmio_hole_size)?,
#[cfg(target_arch = "x86_64")]
gsi_allocator: GsiAllocator::new(apics), gsi_allocator: GsiAllocator::new(apics),
#[cfg(target_arch = "aarch64")]
gsi_allocator: GsiAllocator::new(),
}) })
} }
@@ -81,6 +91,7 @@ impl SystemAllocator {
self.gsi_allocator.allocate_gsi().ok() self.gsi_allocator.allocate_gsi().ok()
} }
#[cfg(target_arch = "x86_64")]
/// Reserves a section of `size` bytes of IO address space. /// Reserves a section of `size` bytes of IO address space.
pub fn allocate_io_addresses( pub fn allocate_io_addresses(
&mut self, &mut self,
@@ -120,6 +131,7 @@ impl SystemAllocator {
) )
} }
#[cfg(target_arch = "x86_64")]
/// Free an IO address range. /// Free an IO address range.
/// We can only free a range if it matches exactly an already allocated range. /// We can only free a range if it matches exactly an already allocated range.
pub fn free_io_addresses(&mut self, address: GuestAddress, size: GuestUsize) { pub fn free_io_addresses(&mut self, address: GuestAddress, size: GuestUsize) {

View File

@@ -10,6 +10,6 @@ thiserror = "1.0"
serde = {version = ">=1.0.27", features = ["rc"] } serde = {version = ">=1.0.27", features = ["rc"] }
serde_derive = ">=1.0.27" serde_derive = ">=1.0.27"
serde_json = ">=1.0.9" serde_json = ">=1.0.9"
vm-memory = { version = "0.1.0", features = ["backend-mmap"] } vm-memory = { version = "0.2.1", features = ["backend-mmap"] }
vmm-sys-util = ">=0.3.1" vmm-sys-util = ">=0.3.1"

View File

@@ -1,116 +1,38 @@
extern crate serde; #[macro_use]
extern crate thiserror; extern crate serde_derive;
extern crate vm_memory; extern crate vm_memory;
pub mod interrupt; pub mod interrupt;
use vm_memory::{ /// Type of Message Singaled Interrupt
Address, GuestAddress, GuestMemory, GuestMemoryMmap, GuestMemoryRegion, GuestRegionMmap, #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
MemoryRegionAddress, pub enum MsiIrqType {
}; /// PCI MSI IRQ numbers.
PciMsi,
use thiserror::Error; /// PCI MSIx IRQ numbers.
PciMsix,
/// Trait meant for triggering the DMA mapping update related to an external /// Generic MSI IRQ numbers.
/// device not managed fully through virtio. It is dedicated to virtio-iommu GenericMsi,
/// in order to trigger the map update anytime the mapping is updated from the
/// guest.
pub trait ExternalDmaMapping: Send + Sync {
/// Map a memory range
fn map(&self, iova: u64, gpa: u64, size: u64) -> std::result::Result<(), std::io::Error>;
/// Unmap a memory range
fn unmap(&self, iova: u64, size: u64) -> std::result::Result<(), std::io::Error>;
} }
#[derive(Error, Debug)] /// Enumeration for device resources.
pub enum MigratableError { #[allow(missing_docs)]
#[error("Failed to pause migratable component: {0}")] #[derive(Clone, Debug, Serialize, Deserialize)]
Pause(#[source] anyhow::Error), pub enum Resource {
/// IO Port address range.
#[error("Failed to resume migratable component: {0}")] PioAddressRange { base: u16, size: u16 },
Resume(#[source] anyhow::Error), /// Memory Mapped IO address range.
} MmioAddressRange { base: u64, size: u64 },
/// Legacy IRQ number.
/// A Pausable component can be paused and resumed. LegacyIrq(u32),
pub trait Pausable { /// Message Signaled Interrupt
/// Pause the component. MsiIrq {
fn pause(&mut self) -> std::result::Result<(), MigratableError>; ty: MsiIrqType,
base: u32,
/// Resume the component. size: u32,
fn resume(&mut self) -> std::result::Result<(), MigratableError>; },
} /// Network Interface Card MAC address.
MacAddress(String),
/// A snapshotable component can be snapshoted. /// KVM memslot index.
pub trait Snapshotable {} KvmMemSlot(u32),
/// Trait to be implemented by any component (device, CPU, RAM, etc) that
/// can be migrated.
/// All migratable components are paused before being snapshotted, and then
/// eventually resumed. Thus any Migratable component must be both Pausable
/// and Snapshotable.
pub trait Migratable: Send + Pausable + Snapshotable {}
fn get_region_host_address_range(
region: &GuestRegionMmap,
addr: MemoryRegionAddress,
size: usize,
) -> Option<*mut u8> {
region.check_address(addr).and_then(|addr| {
region
.checked_offset(addr, size)
.map(|_| region.as_ptr().wrapping_offset(addr.raw_value() as isize))
})
}
/// Convert an absolute address into an address space (GuestMemory)
/// to a host pointer and verify that the provided size define a valid
/// range within a single memory region.
/// Return None if it is out of bounds or if addr+size overlaps a single region.
///
/// This is a temporary vm-memory wrapper.
pub fn get_host_address_range(
mem: &GuestMemoryMmap,
addr: GuestAddress,
size: usize,
) -> Option<*mut u8> {
mem.to_region_addr(addr)
.and_then(|(r, addr)| get_region_host_address_range(r, addr, size))
}
#[cfg(test)]
mod tests {
use super::*;
use vm_memory::{GuestAddress, GuestMemoryMmap};
#[test]
fn test_get_host_address_range() {
let start_addr1 = GuestAddress(0x0);
let start_addr2 = GuestAddress(0x1000);
let guest_mem =
GuestMemoryMmap::from_ranges(&[(start_addr1, 0x400), (start_addr2, 0x400)]).unwrap();
assert!(get_host_address_range(&guest_mem, GuestAddress(0x600), 0x100).is_none());
// Overlapping range
assert!(get_host_address_range(&guest_mem, GuestAddress(0x1000), 0x500).is_none());
// Overlapping range
assert!(get_host_address_range(&guest_mem, GuestAddress(0x1200), 0x500).is_none());
let ptr = get_host_address_range(&guest_mem, GuestAddress(0x1000), 0x100).unwrap();
let ptr0 = get_host_address_range(&guest_mem, GuestAddress(0x1100), 0x100).unwrap();
let ptr1 = guest_mem.get_host_address(GuestAddress(0x1200)).unwrap();
assert_eq!(
ptr,
guest_mem
.find_region(GuestAddress(0x1100))
.unwrap()
.as_ptr()
);
assert_eq!(unsafe { ptr0.offset(0x100) }, ptr1);
}
} }

12
vm-migration/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "vm-migration"
version = "0.1.0"
authors = ["The Cloud Hypervisor Authors"]
edition = "2018"
[dependencies]
anyhow = "1.0"
thiserror = "1.0"
serde = {version = ">=1.0.27", features = ["rc"] }
serde_derive = ">=1.0.27"
serde_json = ">=1.0.9"

162
vm-migration/src/lib.rs Normal file
View File

@@ -0,0 +1,162 @@
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
//
extern crate serde;
extern crate thiserror;
#[macro_use]
extern crate serde_derive;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum MigratableError {
#[error("Failed to pause migratable component: {0}")]
Pause(#[source] anyhow::Error),
#[error("Failed to resume migratable component: {0}")]
Resume(#[source] anyhow::Error),
#[error("Failed to snapshot migratable component: {0}")]
Snapshot(#[source] anyhow::Error),
#[error("Failed to restore migratable component: {0}")]
Restore(#[source] anyhow::Error),
#[error("Failed to send migratable component snapshot: {0}")]
MigrateSend(#[source] anyhow::Error),
#[error("Failed to receive migratable component snapshot: {0}")]
MigrateReceive(#[source] anyhow::Error),
}
/// A Pausable component can be paused and resumed.
pub trait Pausable {
/// Pause the component.
fn pause(&mut self) -> std::result::Result<(), MigratableError> {
Ok(())
}
/// Resume the component.
fn resume(&mut self) -> std::result::Result<(), MigratableError> {
Ok(())
}
}
/// A Snapshottable component snapshot section.
/// Migratable component can split their migration snapshot into
/// separate sections.
/// Splitting a component migration data into different sections
/// allows for easier and forward compatible extensions.
#[derive(Clone, Default, Deserialize, Serialize)]
pub struct SnapshotDataSection {
/// The section id.
pub id: String,
/// The section serialized snapshot.
pub snapshot: Vec<u8>,
}
/// A Snapshottable component's snapshot is a tree of snapshots, where leafs
/// contain the snapshot data. Nodes of this tree track all their children
/// through the snapshots field, which is basically their sub-components.
/// Leaves will typically have an empty snapshots map, while nodes usually
/// carry an empty snapshot_data.
///
/// For example, a device manager snapshot is the composition of all its
/// devices snapshots. The device manager Snapshot would have no snapshot_data
/// but one Snapshot child per tracked device. Then each device's Snapshot
/// would carry an empty snapshots map but a map of SnapshotDataSection, i.e.
/// the actual device snapshot data.
#[derive(Clone, Default, Deserialize, Serialize)]
pub struct Snapshot {
/// The Snapshottable component id.
pub id: String,
/// The Snapshottable component snapshots.
pub snapshots: std::collections::HashMap<String, Box<Snapshot>>,
/// The Snapshottable component's snapshot data.
/// A map of snapshot sections, indexed by the section ids.
pub snapshot_data: std::collections::HashMap<String, SnapshotDataSection>,
}
impl Snapshot {
/// Create an empty Snapshot.
pub fn new(id: &str) -> Self {
Snapshot {
id: id.to_string(),
..Default::default()
}
}
/// Add a sub-component's Snapshot to the Snapshot.
pub fn add_snapshot(&mut self, snapshot: Snapshot) {
self.snapshots
.insert(snapshot.id.clone(), Box::new(snapshot));
}
/// Add a SnapshotDatasection to the component snapshot data.
pub fn add_data_section(&mut self, section: SnapshotDataSection) {
self.snapshot_data.insert(section.id.clone(), section);
}
}
/// A snapshottable component can be snapshotted.
pub trait Snapshottable: Pausable {
/// The snapshottable component id.
fn id(&self) -> String {
String::new()
}
/// Take a component snapshot.
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
Ok(Snapshot::new(""))
}
/// Restore a component from its snapshot.
fn restore(&mut self, _snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
Ok(())
}
}
/// A transportable component can be sent or receive to a specific URL.
///
/// This trait is meant to be used for component that have custom
/// transport handlers.
pub trait Transportable: Pausable + Snapshottable {
/// Send a component snapshot.
///
/// # Arguments
///
/// * `snapshot` - The migratable component snapshot to send.
/// * `destination_url` - The destination URL to send the snapshot to. This
/// could be an HTTP endpoint, a TCP address or a local file.
fn send(
&self,
_snapshot: &Snapshot,
_destination_url: &str,
) -> std::result::Result<(), MigratableError> {
Ok(())
}
/// Receive a component snapshot.
///
/// # Arguments
///
/// * `source_url` - The source URL to fetch the snapshot from. This could be an HTTP
/// endpoint, a TCP address or a local file.
fn recv(&self, _source_url: &str) -> std::result::Result<Snapshot, MigratableError> {
Ok(Snapshot::new(""))
}
}
/// Trait to be implemented by any component (device, CPU, RAM, etc) that
/// can be migrated.
/// All migratable components are paused before being snapshotted, and then
/// eventually resumed. Thus any Migratable component must be both Pausable
/// and Snapshottable.
/// Moreover a migratable component can be transported to a remote or local
/// destination and thus must be Transportable.
pub trait Migratable: Send + Pausable + Snapshottable + Transportable {}

View File

@@ -10,19 +10,25 @@ pci_support = ["pci"]
mmio_support = [] mmio_support = []
[dependencies] [dependencies]
anyhow = "1.0"
arc-swap = ">=0.4.4" arc-swap = ">=0.4.4"
byteorder = "1.3.4" byteorder = "1.3.4"
devices = { path = "../devices" } devices = { path = "../devices" }
epoll = ">=4.0.1" epoll = ">=4.0.1"
libc = "0.2.68" libc = "0.2.71"
log = "0.4.8" log = "0.4.8"
net_gen = { path = "../net_gen" } net_gen = { path = "../net_gen" }
net_util = { path = "../net_util" } net_util = { path = "../net_util" }
pci = { path = "../pci", optional = true } pci = { path = "../pci", optional = true }
serde = ">=1.0.27"
serde_derive = ">=1.0.27"
serde_json = ">=1.0.9"
tempfile = "3.1.0" tempfile = "3.1.0"
virtio-bindings = { git = "https://github.com/rust-vmm/virtio-bindings", version = "0.1", features = ["virtio-v5_0_0"]} virtio-bindings = { version = "0.1", features = ["virtio-v5_0_0"]}
vfio-ioctls = { git = "https://github.com/cloud-hypervisor/vfio-ioctls", branch = "ch" }
vm-allocator = { path = "../vm-allocator" } vm-allocator = { path = "../vm-allocator" }
vm-device = { path = "../vm-device" } vm-device = { path = "../vm-device" }
vm-memory = { version = "0.1.0", features = ["backend-mmap", "backend-atomic"] } vm-memory = { version = "0.2.1", features = ["backend-mmap", "backend-atomic"] }
vm-migration = { path = "../vm-migration" }
vmm-sys-util = ">=0.3.1" vmm-sys-util = ">=0.3.1"
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-master", "vhost-user-slave"] } vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-master", "vhost-user-slave"] }

263
vm-virtio/src/block.rs Executable file → Normal file
View File

@@ -14,8 +14,9 @@ use super::{
VirtioDeviceType, VirtioInterruptType, VirtioDeviceType, VirtioInterruptType,
}; };
use crate::VirtioInterrupt; use crate::VirtioInterrupt;
use epoll; use anyhow::anyhow;
use libc::{c_void, EFD_NONBLOCK}; use libc::{c_void, EFD_NONBLOCK};
use serde::ser::{Serialize, SerializeStruct, Serializer};
use std::alloc::{alloc_zeroed, dealloc, Layout}; use std::alloc::{alloc_zeroed, dealloc, Layout};
use std::cmp; use std::cmp;
use std::convert::TryInto; use std::convert::TryInto;
@@ -23,7 +24,7 @@ use std::fs::{File, Metadata};
use std::io::{self, Read, Seek, SeekFrom, Write}; use std::io::{self, Read, Seek, SeekFrom, Write};
use std::ops::DerefMut; use std::ops::DerefMut;
use std::os::linux::fs::MetadataExt; use std::os::linux::fs::MetadataExt;
use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::path::PathBuf; use std::path::PathBuf;
use std::result; use std::result;
use std::slice; use std::slice;
@@ -31,11 +32,15 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::thread; use std::thread;
use virtio_bindings::bindings::virtio_blk::*; use virtio_bindings::bindings::virtio_blk::*;
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable}; use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
use vm_memory::{ use vm_memory::{
ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
GuestMemoryError, GuestMemoryMmap, GuestMemoryError, GuestMemoryMmap,
}; };
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
Transportable,
};
use vmm_sys_util::{eventfd::EventFd, seek_hole::SeekHole, write_zeroes::PunchHole}; use vmm_sys_util::{eventfd::EventFd, seek_hole::SeekHole, write_zeroes::PunchHole};
const SECTOR_SHIFT: u8 = 9; const SECTOR_SHIFT: u8 = 9;
@@ -344,7 +349,7 @@ impl Write for RawFile {
} }
fn flush(&mut self) -> std::io::Result<()> { fn flush(&mut self) -> std::io::Result<()> {
self.file.flush() self.file.sync_all()
} }
} }
@@ -473,6 +478,7 @@ pub struct Request {
data_addr: GuestAddress, data_addr: GuestAddress,
data_len: u32, data_len: u32,
pub status_addr: GuestAddress, pub status_addr: GuestAddress,
writeback: bool,
} }
impl Request { impl Request {
@@ -491,6 +497,7 @@ impl Request {
data_addr: GuestAddress(0), data_addr: GuestAddress(0),
data_len: 0, data_len: 0,
status_addr: GuestAddress(0), status_addr: GuestAddress(0),
writeback: true,
}; };
let data_desc; let data_desc;
@@ -570,13 +577,11 @@ impl Request {
RequestType::Out => { RequestType::Out => {
mem.write_all_to(self.data_addr, disk, self.data_len as usize) mem.write_all_to(self.data_addr, disk, self.data_len as usize)
.map_err(ExecuteError::Write)?; .map_err(ExecuteError::Write)?;
} if !self.writeback {
RequestType::Flush => match disk.flush() { disk.flush().map_err(ExecuteError::Flush)?;
Ok(_) => {
return Ok(0);
} }
Err(e) => return Err(ExecuteError::Flush(e)), }
}, RequestType::Flush => disk.flush().map_err(ExecuteError::Flush)?,
RequestType::GetDeviceID => { RequestType::GetDeviceID => {
if (self.data_len as usize) < disk_id.len() { if (self.data_len as usize) < disk_id.len() {
return Err(ExecuteError::BadRequest(Error::InvalidOffset)); return Err(ExecuteError::BadRequest(Error::InvalidOffset));
@@ -588,6 +593,10 @@ impl Request {
}; };
Ok(0) Ok(0)
} }
pub fn set_writeback(&mut self, writeback: bool) {
self.writeback = writeback
}
} }
struct BlockEpollHandler<T: DiskFile> { struct BlockEpollHandler<T: DiskFile> {
@@ -599,6 +608,8 @@ struct BlockEpollHandler<T: DiskFile> {
disk_image_id: Vec<u8>, disk_image_id: Vec<u8>,
kill_evt: EventFd, kill_evt: EventFd,
pause_evt: EventFd, pause_evt: EventFd,
event_idx: bool,
writeback: Arc<AtomicBool>,
} }
impl<T: DiskFile> BlockEpollHandler<T> { impl<T: DiskFile> BlockEpollHandler<T> {
@@ -611,7 +622,9 @@ impl<T: DiskFile> BlockEpollHandler<T> {
for avail_desc in queue.iter(&mem) { for avail_desc in queue.iter(&mem) {
let len; let len;
match Request::parse(&avail_desc, &mem) { match Request::parse(&avail_desc, &mem) {
Ok(request) => { Ok(mut request) => {
request.set_writeback(self.writeback.load(Ordering::SeqCst));
let mut disk_image_locked = self.disk_image.lock().unwrap(); let mut disk_image_locked = self.disk_image.lock().unwrap();
let mut disk_image = disk_image_locked.deref_mut(); let mut disk_image = disk_image_locked.deref_mut();
let status = match request.execute( let status = match request.execute(
@@ -680,24 +693,26 @@ impl<T: DiskFile> BlockEpollHandler<T> {
) -> result::Result<(), DeviceError> { ) -> result::Result<(), DeviceError> {
// Create the epoll file descriptor // Create the epoll file descriptor
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?; let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
// Use 'File' to enforce closing on 'epoll_fd'
let epoll_file = unsafe { File::from_raw_fd(epoll_fd) };
// Add events // Add events
epoll::ctl( epoll::ctl(
epoll_fd, epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_ADD, epoll::ControlOptions::EPOLL_CTL_ADD,
queue_evt.as_raw_fd(), queue_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(QUEUE_AVAIL_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(QUEUE_AVAIL_EVENT)),
) )
.map_err(DeviceError::EpollCtl)?; .map_err(DeviceError::EpollCtl)?;
epoll::ctl( epoll::ctl(
epoll_fd, epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_ADD, epoll::ControlOptions::EPOLL_CTL_ADD,
self.kill_evt.as_raw_fd(), self.kill_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
) )
.map_err(DeviceError::EpollCtl)?; .map_err(DeviceError::EpollCtl)?;
epoll::ctl( epoll::ctl(
epoll_fd, epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_ADD, epoll::ControlOptions::EPOLL_CTL_ADD,
self.pause_evt.as_raw_fd(), self.pause_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)),
@@ -708,7 +723,7 @@ impl<T: DiskFile> BlockEpollHandler<T> {
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
'epoll: loop { 'epoll: loop {
let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) { let num_events = match epoll::wait(epoll_file.as_raw_fd(), -1, &mut events[..]) {
Ok(res) => res, Ok(res) => res,
Err(e) => { Err(e) => {
if e.kind() == io::ErrorKind::Interrupted { if e.kind() == io::ErrorKind::Interrupted {
@@ -733,6 +748,28 @@ impl<T: DiskFile> BlockEpollHandler<T> {
if let Err(e) = queue_evt.read() { if let Err(e) = queue_evt.read() {
error!("Failed to get queue event: {:?}", e); error!("Failed to get queue event: {:?}", e);
break 'epoll; break 'epoll;
} else if self.event_idx {
// vm-virtio's Queue implementation only checks avail_index
// once, so to properly support EVENT_IDX we need to keep
// calling process_queue() until it stops finding new
// requests on the queue.
loop {
if self.process_queue() {
self.queue.update_avail_event(&self.mem.memory());
if self.queue.needs_notification(
&self.mem.memory(),
self.queue.next_used,
) {
if let Err(e) = self.signal_used_queue() {
error!("Failed to signal used queue: {:?}", e);
break 'epoll;
}
}
} else {
break;
}
}
} else if self.process_queue() { } else if self.process_queue() {
if let Err(e) = self.signal_used_queue() { if let Err(e) = self.signal_used_queue() {
error!("Failed to signal used queue: {:?}", e); error!("Failed to signal used queue: {:?}", e);
@@ -766,7 +803,7 @@ impl<T: DiskFile> BlockEpollHandler<T> {
} }
} }
#[derive(Copy, Clone, Debug, Default)] #[derive(Copy, Clone, Debug, Default, Deserialize)]
#[repr(C, packed)] #[repr(C, packed)]
pub struct VirtioBlockGeometry { pub struct VirtioBlockGeometry {
pub cylinders: u16, pub cylinders: u16,
@@ -774,9 +811,31 @@ pub struct VirtioBlockGeometry {
pub sectors: u8, pub sectors: u8,
} }
// We must explicitly implement Serialize since the structure is packed and
// it's unsafe to borrow from a packed structure. And by default, if we derive
// Serialize from serde, it will borrow the values from the structure.
// That's why this implementation copies each field separately before it
// serializes the entire structure field by field.
impl Serialize for VirtioBlockGeometry {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let cylinders = self.cylinders;
let heads = self.heads;
let sectors = self.sectors;
let mut virtio_block_geometry = serializer.serialize_struct("VirtioBlockGeometry", 4)?;
virtio_block_geometry.serialize_field("cylinders", &cylinders)?;
virtio_block_geometry.serialize_field("heads", &heads)?;
virtio_block_geometry.serialize_field("sectors", &sectors)?;
virtio_block_geometry.end()
}
}
unsafe impl ByteValued for VirtioBlockGeometry {} unsafe impl ByteValued for VirtioBlockGeometry {}
#[derive(Copy, Clone, Debug, Default)] #[derive(Copy, Clone, Debug, Default, Deserialize)]
#[repr(C, packed)] #[repr(C, packed)]
pub struct VirtioBlockConfig { pub struct VirtioBlockConfig {
pub capacity: u64, pub capacity: u64,
@@ -788,7 +847,7 @@ pub struct VirtioBlockConfig {
pub alignment_offset: u8, pub alignment_offset: u8,
pub min_io_size: u16, pub min_io_size: u16,
pub opt_io_size: u32, pub opt_io_size: u32,
pub wce: u8, pub writeback: u8,
unused: u8, unused: u8,
pub num_queues: u16, pub num_queues: u16,
pub max_discard_sectors: u32, pub max_discard_sectors: u32,
@@ -800,10 +859,67 @@ pub struct VirtioBlockConfig {
unused1: [u8; 3], unused1: [u8; 3],
} }
// We must explicitly implement Serialize since the structure is packed and
// it's unsafe to borrow from a packed structure. And by default, if we derive
// Serialize from serde, it will borrow the values from the structure.
// That's why this implementation copies each field separately before it
// serializes the entire structure field by field.
impl Serialize for VirtioBlockConfig {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let capacity = self.capacity;
let size_max = self.size_max;
let seg_max = self.seg_max;
let geometry = self.geometry;
let blk_size = self.blk_size;
let physical_block_exp = self.physical_block_exp;
let alignment_offset = self.alignment_offset;
let min_io_size = self.min_io_size;
let opt_io_size = self.opt_io_size;
let writeback = self.writeback;
let unused = self.unused;
let num_queues = self.num_queues;
let max_discard_sectors = self.max_discard_sectors;
let max_discard_seg = self.max_discard_seg;
let discard_sector_alignment = self.discard_sector_alignment;
let max_write_zeroes_sectors = self.max_write_zeroes_sectors;
let max_write_zeroes_seg = self.max_write_zeroes_seg;
let write_zeroes_may_unmap = self.write_zeroes_may_unmap;
let unused1 = self.unused1;
let mut virtio_block_config = serializer.serialize_struct("VirtioBlockConfig", 60)?;
virtio_block_config.serialize_field("capacity", &capacity)?;
virtio_block_config.serialize_field("size_max", &size_max)?;
virtio_block_config.serialize_field("seg_max", &seg_max)?;
virtio_block_config.serialize_field("geometry", &geometry)?;
virtio_block_config.serialize_field("blk_size", &blk_size)?;
virtio_block_config.serialize_field("physical_block_exp", &physical_block_exp)?;
virtio_block_config.serialize_field("alignment_offset", &alignment_offset)?;
virtio_block_config.serialize_field("min_io_size", &min_io_size)?;
virtio_block_config.serialize_field("opt_io_size", &opt_io_size)?;
virtio_block_config.serialize_field("writeback", &writeback)?;
virtio_block_config.serialize_field("unused", &unused)?;
virtio_block_config.serialize_field("num_queues", &num_queues)?;
virtio_block_config.serialize_field("max_discard_sectors", &max_discard_sectors)?;
virtio_block_config.serialize_field("max_discard_seg", &max_discard_seg)?;
virtio_block_config
.serialize_field("discard_sector_alignment", &discard_sector_alignment)?;
virtio_block_config
.serialize_field("max_write_zeroes_sectors", &max_write_zeroes_sectors)?;
virtio_block_config.serialize_field("max_write_zeroes_seg", &max_write_zeroes_seg)?;
virtio_block_config.serialize_field("write_zeroes_may_unmap", &write_zeroes_may_unmap)?;
virtio_block_config.serialize_field("unused1", &unused1)?;
virtio_block_config.end()
}
}
unsafe impl ByteValued for VirtioBlockConfig {} unsafe impl ByteValued for VirtioBlockConfig {}
/// Virtio device for exposing block level read/write operations on a host file. /// Virtio device for exposing block level read/write operations on a host file.
pub struct Block<T: DiskFile> { pub struct Block<T: DiskFile> {
id: String,
kill_evt: Option<EventFd>, kill_evt: Option<EventFd>,
disk_image: Arc<Mutex<T>>, disk_image: Arc<Mutex<T>>,
disk_path: PathBuf, disk_path: PathBuf,
@@ -817,6 +933,16 @@ pub struct Block<T: DiskFile> {
pause_evt: Option<EventFd>, pause_evt: Option<EventFd>,
paused: Arc<AtomicBool>, paused: Arc<AtomicBool>,
queue_size: Vec<u16>, queue_size: Vec<u16>,
writeback: Arc<AtomicBool>,
}
#[derive(Serialize, Deserialize)]
pub struct BlockState {
pub disk_path: PathBuf,
pub disk_nsectors: u64,
pub avail_features: u64,
pub acked_features: u64,
pub config: VirtioBlockConfig,
} }
impl<T: DiskFile> Block<T> { impl<T: DiskFile> Block<T> {
@@ -824,6 +950,7 @@ impl<T: DiskFile> Block<T> {
/// ///
/// The given file must be seekable and sizable. /// The given file must be seekable and sizable.
pub fn new( pub fn new(
id: String,
mut disk_image: T, mut disk_image: T,
disk_path: PathBuf, disk_path: PathBuf,
is_disk_read_only: bool, is_disk_read_only: bool,
@@ -840,7 +967,10 @@ impl<T: DiskFile> Block<T> {
); );
} }
let mut avail_features = (1u64 << VIRTIO_F_VERSION_1) | (1u64 << VIRTIO_BLK_F_FLUSH); let mut avail_features = (1u64 << VIRTIO_F_VERSION_1)
| (1u64 << VIRTIO_BLK_F_FLUSH)
| (1u64 << VIRTIO_RING_F_EVENT_IDX)
| (1u64 << VIRTIO_BLK_F_CONFIG_WCE);
if iommu { if iommu {
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM; avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
@@ -853,6 +983,7 @@ impl<T: DiskFile> Block<T> {
let disk_nsectors = disk_size / SECTOR_SIZE; let disk_nsectors = disk_size / SECTOR_SIZE;
let mut config = VirtioBlockConfig { let mut config = VirtioBlockConfig {
capacity: disk_nsectors, capacity: disk_nsectors,
writeback: 1,
..Default::default() ..Default::default()
}; };
@@ -862,6 +993,7 @@ impl<T: DiskFile> Block<T> {
} }
Ok(Block { Ok(Block {
id,
kill_evt: None, kill_evt: None,
disk_image: Arc::new(Mutex::new(disk_image)), disk_image: Arc::new(Mutex::new(disk_image)),
disk_path, disk_path,
@@ -875,8 +1007,50 @@ impl<T: DiskFile> Block<T> {
pause_evt: None, pause_evt: None,
paused: Arc::new(AtomicBool::new(false)), paused: Arc::new(AtomicBool::new(false)),
queue_size: vec![queue_size; num_queues], queue_size: vec![queue_size; num_queues],
writeback: Arc::new(AtomicBool::new(true)),
}) })
} }
fn state(&self) -> BlockState {
BlockState {
disk_path: self.disk_path.clone(),
disk_nsectors: self.disk_nsectors,
avail_features: self.avail_features,
acked_features: self.acked_features,
config: self.config,
}
}
fn set_state(&mut self, state: &BlockState) -> io::Result<()> {
self.disk_path = state.disk_path.clone();
self.disk_nsectors = state.disk_nsectors;
self.avail_features = state.avail_features;
self.acked_features = state.acked_features;
self.config = state.config;
Ok(())
}
fn update_writeback(&mut self) {
// Use writeback from config if VIRTIO_BLK_F_CONFIG_WCE
let writeback =
if self.acked_features & 1 << VIRTIO_BLK_F_CONFIG_WCE == 1 << VIRTIO_BLK_F_CONFIG_WCE {
self.config.writeback == 1
} else {
// Else check if VIRTIO_BLK_F_FLUSH negotiated
self.acked_features & 1 << VIRTIO_BLK_F_FLUSH == 1 << VIRTIO_BLK_F_FLUSH
};
info!(
"Changing cache mode to {}",
if writeback {
"writeback"
} else {
"writethrough"
}
);
self.writeback.store(writeback, Ordering::SeqCst);
}
} }
impl<T: DiskFile> Drop for Block<T> { impl<T: DiskFile> Drop for Block<T> {
@@ -938,6 +1112,7 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
} }
let (_, right) = config_slice.split_at_mut(offset as usize); let (_, right) = config_slice.split_at_mut(offset as usize);
right.copy_from_slice(&data[..]); right.copy_from_slice(&data[..]);
self.update_writeback();
} }
fn activate( fn activate(
@@ -997,6 +1172,10 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
} }
self.queue_evts = Some(tmp_queue_evts); self.queue_evts = Some(tmp_queue_evts);
let event_idx = self.acked_features & 1u64 << VIRTIO_RING_F_EVENT_IDX
== 1u64 << VIRTIO_RING_F_EVENT_IDX;
self.update_writeback();
let mut epoll_threads = Vec::new(); let mut epoll_threads = Vec::new();
for _ in 0..self.queue_size.len() { for _ in 0..self.queue_size.len() {
let mut handler = BlockEpollHandler { let mut handler = BlockEpollHandler {
@@ -1008,8 +1187,12 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
disk_image_id: disk_image_id.clone(), disk_image_id: disk_image_id.clone(),
kill_evt: kill_evt.try_clone().unwrap(), kill_evt: kill_evt.try_clone().unwrap(),
pause_evt: pause_evt.try_clone().unwrap(), pause_evt: pause_evt.try_clone().unwrap(),
event_idx,
writeback: self.writeback.clone(),
}; };
handler.queue.set_event_idx(event_idx);
let queue_evt = queue_evts.remove(0); let queue_evt = queue_evts.remove(0);
let paused = self.paused.clone(); let paused = self.paused.clone();
thread::Builder::new() thread::Builder::new()
@@ -1051,5 +1234,45 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
} }
virtio_pausable!(Block, T: 'static + DiskFile + Send); virtio_pausable!(Block, T: 'static + DiskFile + Send);
impl<T: 'static + DiskFile + Send> Snapshotable for Block<T> {} impl<T: 'static + DiskFile + Send> Snapshottable for Block<T> {
fn id(&self) -> String {
self.id.clone()
}
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
let snapshot =
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
let mut block_snapshot = Snapshot::new(self.id.as_str());
block_snapshot.add_data_section(SnapshotDataSection {
id: format!("{}-section", self.id),
snapshot,
});
Ok(block_snapshot)
}
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
if let Some(block_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
let block_state = match serde_json::from_slice(&block_section.snapshot) {
Ok(state) => state,
Err(error) => {
return Err(MigratableError::Restore(anyhow!(
"Could not deserialize BLOCK {}",
error
)))
}
};
return self.set_state(&block_state).map_err(|e| {
MigratableError::Restore(anyhow!("Could not restore BLOCK state {:?}", e))
});
}
Err(MigratableError::Restore(anyhow!(
"Could not find BLOCK snapshot section"
)))
}
}
impl<T: 'static + DiskFile + Send> Transportable for Block<T> {}
impl<T: 'static + DiskFile + Send> Migratable for Block<T> {} impl<T: 'static + DiskFile + Send> Migratable for Block<T> {}

127
vm-virtio/src/console.rs Executable file → Normal file
View File

@@ -7,21 +7,25 @@ use super::{
VirtioInterruptType, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1, VirtioInterruptType, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
}; };
use crate::VirtioInterrupt; use crate::VirtioInterrupt;
use epoll; use anyhow::anyhow;
use libc::EFD_NONBLOCK; use libc::EFD_NONBLOCK;
use std; use serde::ser::{Serialize, SerializeStruct, Serializer};
use std::cmp; use std::cmp;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::fs::File;
use std::io; use std::io;
use std::io::Write; use std::io::Write;
use std::ops::DerefMut; use std::ops::DerefMut;
use std::os::unix::io::AsRawFd; use std::os::unix::io::{AsRawFd, FromRawFd};
use std::result; use std::result;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::thread; use std::thread;
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
use vm_memory::{ByteValued, Bytes, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap}; use vm_memory::{ByteValued, Bytes, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
Transportable,
};
use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::eventfd::EventFd;
const QUEUE_SIZE: u16 = 256; const QUEUE_SIZE: u16 = 256;
@@ -43,8 +47,8 @@ const PAUSE_EVENT: DeviceEventT = 5;
//Console size feature bit //Console size feature bit
const VIRTIO_CONSOLE_F_SIZE: u64 = 0; const VIRTIO_CONSOLE_F_SIZE: u64 = 0;
#[derive(Copy, Clone, Debug, Default)] #[derive(Copy, Clone, Debug, Default, Deserialize)]
#[repr(C)] #[repr(C, packed)]
pub struct VirtioConsoleConfig { pub struct VirtioConsoleConfig {
cols: u16, cols: u16,
rows: u16, rows: u16,
@@ -52,6 +56,30 @@ pub struct VirtioConsoleConfig {
emerg_wr: u32, emerg_wr: u32,
} }
// We must explicitly implement Serialize since the structure is packed and
// it's unsafe to borrow from a packed structure. And by default, if we derive
// Serialize from serde, it will borrow the values from the structure.
// That's why this implementation copies each field separately before it
// serializes the entire structure field by field.
impl Serialize for VirtioConsoleConfig {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let cols = self.cols;
let rows = self.rows;
let max_nr_ports = self.max_nr_ports;
let emerg_wr = self.emerg_wr;
let mut virtio_console_config = serializer.serialize_struct("VirtioConsoleConfig", 12)?;
virtio_console_config.serialize_field("cols", &cols)?;
virtio_console_config.serialize_field("rows", &rows)?;
virtio_console_config.serialize_field("max_nr_ports", &max_nr_ports)?;
virtio_console_config.serialize_field("emerg_wr", &emerg_wr)?;
virtio_console_config.end()
}
}
// Safe because it only has data and has no implicit padding. // Safe because it only has data and has no implicit padding.
unsafe impl ByteValued for VirtioConsoleConfig {} unsafe impl ByteValued for VirtioConsoleConfig {}
@@ -157,31 +185,33 @@ impl ConsoleEpollHandler {
fn run(&mut self, paused: Arc<AtomicBool>) -> result::Result<(), DeviceError> { fn run(&mut self, paused: Arc<AtomicBool>) -> result::Result<(), DeviceError> {
// Create the epoll file descriptor // Create the epoll file descriptor
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?; let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
// Use 'File' to enforce closing on 'epoll_fd'
let epoll_file = unsafe { File::from_raw_fd(epoll_fd) };
// Add events // Add events
epoll::ctl( epoll::ctl(
epoll_fd, epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_ADD, epoll::ControlOptions::EPOLL_CTL_ADD,
self.input_queue_evt.as_raw_fd(), self.input_queue_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(INPUT_QUEUE_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(INPUT_QUEUE_EVENT)),
) )
.map_err(DeviceError::EpollCtl)?; .map_err(DeviceError::EpollCtl)?;
epoll::ctl( epoll::ctl(
epoll_fd, epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_ADD, epoll::ControlOptions::EPOLL_CTL_ADD,
self.output_queue_evt.as_raw_fd(), self.output_queue_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(OUTPUT_QUEUE_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(OUTPUT_QUEUE_EVENT)),
) )
.map_err(DeviceError::EpollCtl)?; .map_err(DeviceError::EpollCtl)?;
epoll::ctl( epoll::ctl(
epoll_fd, epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_ADD, epoll::ControlOptions::EPOLL_CTL_ADD,
self.input_evt.as_raw_fd(), self.input_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(INPUT_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(INPUT_EVENT)),
) )
.map_err(DeviceError::EpollCtl)?; .map_err(DeviceError::EpollCtl)?;
epoll::ctl( epoll::ctl(
epoll_fd, epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_ADD, epoll::ControlOptions::EPOLL_CTL_ADD,
self.config_evt.as_raw_fd(), self.config_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(CONFIG_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(CONFIG_EVENT)),
@@ -189,14 +219,14 @@ impl ConsoleEpollHandler {
.map_err(DeviceError::EpollCtl)?; .map_err(DeviceError::EpollCtl)?;
epoll::ctl( epoll::ctl(
epoll_fd, epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_ADD, epoll::ControlOptions::EPOLL_CTL_ADD,
self.kill_evt.as_raw_fd(), self.kill_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
) )
.map_err(DeviceError::EpollCtl)?; .map_err(DeviceError::EpollCtl)?;
epoll::ctl( epoll::ctl(
epoll_fd, epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_ADD, epoll::ControlOptions::EPOLL_CTL_ADD,
self.pause_evt.as_raw_fd(), self.pause_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)),
@@ -207,7 +237,7 @@ impl ConsoleEpollHandler {
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
'epoll: loop { 'epoll: loop {
let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) { let num_events = match epoll::wait(epoll_file.as_raw_fd(), -1, &mut events[..]) {
Ok(res) => res, Ok(res) => res,
Err(e) => { Err(e) => {
if e.kind() == io::ErrorKind::Interrupted { if e.kind() == io::ErrorKind::Interrupted {
@@ -343,6 +373,7 @@ impl VirtioConsoleConfig {
/// Virtio device for exposing console to the guest OS through virtio. /// Virtio device for exposing console to the guest OS through virtio.
pub struct Console { pub struct Console {
id: String,
kill_evt: Option<EventFd>, kill_evt: Option<EventFd>,
pause_evt: Option<EventFd>, pause_evt: Option<EventFd>,
avail_features: u64, avail_features: u64,
@@ -356,9 +387,18 @@ pub struct Console {
paused: Arc<AtomicBool>, paused: Arc<AtomicBool>,
} }
#[derive(Serialize, Deserialize)]
pub struct ConsoleState {
avail_features: u64,
acked_features: u64,
config: VirtioConsoleConfig,
in_buffer: VecDeque<u8>,
}
impl Console { impl Console {
/// Create a new virtio console device that gets random data from /dev/urandom. /// Create a new virtio console device that gets random data from /dev/urandom.
pub fn new( pub fn new(
id: String,
out: Box<dyn io::Write + Send + Sync + 'static>, out: Box<dyn io::Write + Send + Sync + 'static>,
cols: u16, cols: u16,
rows: u16, rows: u16,
@@ -383,6 +423,7 @@ impl Console {
Ok(( Ok((
Console { Console {
id,
kill_evt: None, kill_evt: None,
pause_evt: None, pause_evt: None,
avail_features, avail_features,
@@ -398,6 +439,24 @@ impl Console {
console_input, console_input,
)) ))
} }
fn state(&self) -> ConsoleState {
ConsoleState {
avail_features: self.avail_features,
acked_features: self.acked_features,
config: *(self.config.lock().unwrap()),
in_buffer: self.input.in_buffer.lock().unwrap().clone(),
}
}
fn set_state(&mut self, state: &ConsoleState) -> io::Result<()> {
self.avail_features = state.avail_features;
self.acked_features = state.acked_features;
*(self.config.lock().unwrap()) = state.config;
*(self.input.in_buffer.lock().unwrap()) = state.in_buffer.clone();
Ok(())
}
} }
impl Drop for Console { impl Drop for Console {
@@ -563,5 +622,45 @@ impl VirtioDevice for Console {
} }
virtio_pausable!(Console); virtio_pausable!(Console);
impl Snapshotable for Console {} impl Snapshottable for Console {
fn id(&self) -> String {
self.id.clone()
}
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
let snapshot =
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
let mut console_snapshot = Snapshot::new(self.id.as_str());
console_snapshot.add_data_section(SnapshotDataSection {
id: format!("{}-section", self.id),
snapshot,
});
Ok(console_snapshot)
}
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
if let Some(console_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
let console_state = match serde_json::from_slice(&console_section.snapshot) {
Ok(state) => state,
Err(error) => {
return Err(MigratableError::Restore(anyhow!(
"Could not deserialize CONSOLE {}",
error
)))
}
};
return self.set_state(&console_state).map_err(|e| {
MigratableError::Restore(anyhow!("Could not restore CONSOLE state {:?}", e))
});
}
Err(MigratableError::Restore(anyhow!(
"Could not find CONSOLE snapshot section"
)))
}
}
impl Transportable for Console {}
impl Migratable for Console {} impl Migratable for Console {}

View File

@@ -6,7 +6,7 @@
// //
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use super::*; use crate::{ActivateResult, Error, Queue};
use std::sync::Arc; use std::sync::Arc;
use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestMemoryMmap, GuestUsize}; use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestMemoryMmap, GuestUsize};
use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::eventfd::EventFd;
@@ -34,6 +34,15 @@ pub trait VirtioInterrupt: Send + Sync {
pub type VirtioIommuRemapping = pub type VirtioIommuRemapping =
Box<dyn Fn(u64) -> std::result::Result<u64, std::io::Error> + Send + Sync>; Box<dyn Fn(u64) -> std::result::Result<u64, std::io::Error> + Send + Sync>;
#[derive(Clone)]
pub struct UserspaceMapping {
pub host_addr: u64,
pub mem_slot: u32,
pub addr: GuestAddress,
pub len: GuestUsize,
pub mergeable: bool,
}
#[derive(Clone)] #[derive(Clone)]
pub struct VirtioSharedMemory { pub struct VirtioSharedMemory {
pub offset: u64, pub offset: u64,
@@ -42,6 +51,8 @@ pub struct VirtioSharedMemory {
#[derive(Clone)] #[derive(Clone)]
pub struct VirtioSharedMemoryList { pub struct VirtioSharedMemoryList {
pub host_addr: u64,
pub mem_slot: u32,
pub addr: GuestAddress, pub addr: GuestAddress,
pub len: GuestUsize, pub len: GuestUsize,
pub region_list: Vec<VirtioSharedMemory>, pub region_list: Vec<VirtioSharedMemory>,
@@ -97,6 +108,14 @@ pub trait VirtioDevice: Send {
None None
} }
/// Updates the list of shared memory regions required by the device.
fn set_shm_regions(
&mut self,
_shm_regions: VirtioSharedMemoryList,
) -> std::result::Result<(), Error> {
std::unimplemented!()
}
fn iommu_translate(&self, addr: u64) -> u64 { fn iommu_translate(&self, addr: u64) -> u64 {
addr addr
} }
@@ -106,6 +125,15 @@ pub trait VirtioDevice: Send {
/// every device as part of shutting down the VM. Acting on the device /// every device as part of shutting down the VM. Acting on the device
/// after a shutdown() can lead to unpredictable results. /// after a shutdown() can lead to unpredictable results.
fn shutdown(&mut self) {} fn shutdown(&mut self) {}
fn update_memory(&mut self, _mem: &GuestMemoryMmap) -> std::result::Result<(), Error> {
Ok(())
}
/// Returns the list of userspace mappings associated with this device.
fn userspace_mappings(&self) -> Vec<UserspaceMapping> {
Vec::new()
}
} }
/// Trait providing address translation the same way a physical DMA remapping /// Trait providing address translation the same way a physical DMA remapping

View File

@@ -8,24 +8,29 @@ use super::{
VirtioDeviceType, VIRTIO_F_VERSION_1, VirtioDeviceType, VIRTIO_F_VERSION_1,
}; };
use crate::{DmaRemapping, VirtioInterrupt, VirtioInterruptType}; use crate::{DmaRemapping, VirtioInterrupt, VirtioInterruptType};
use epoll; use anyhow::anyhow;
use libc::EFD_NONBLOCK; use libc::EFD_NONBLOCK;
use std::cmp; use std::cmp;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::fmt::{self, Display}; use std::fmt::{self, Display};
use std::fs::File;
use std::io::{self, Write}; use std::io::{self, Write};
use std::mem::size_of; use std::mem::size_of;
use std::ops::Bound::Included; use std::ops::Bound::Included;
use std::os::unix::io::AsRawFd; use std::os::unix::io::{AsRawFd, FromRawFd};
use std::result; use std::result;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use std::thread; use std::thread;
use vm_device::{ExternalDmaMapping, Migratable, MigratableError, Pausable, Snapshotable}; use vfio_ioctls::ExternalDmaMapping;
use vm_memory::{ use vm_memory::{
Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic,
GuestMemoryError, GuestMemoryMmap, GuestMemoryError, GuestMemoryMmap,
}; };
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
Transportable,
};
use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::eventfd::EventFd;
/// Queues sizes /// Queues sizes
@@ -646,31 +651,33 @@ impl IommuEpollHandler {
fn run(&mut self, paused: Arc<AtomicBool>) -> result::Result<(), DeviceError> { fn run(&mut self, paused: Arc<AtomicBool>) -> result::Result<(), DeviceError> {
// Create the epoll file descriptor // Create the epoll file descriptor
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?; let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
// Use 'File' to enforce closing on 'epoll_fd'
let epoll_file = unsafe { File::from_raw_fd(epoll_fd) };
// Add events // Add events
epoll::ctl( epoll::ctl(
epoll_fd, epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_ADD, epoll::ControlOptions::EPOLL_CTL_ADD,
self.queue_evts[0].as_raw_fd(), self.queue_evts[0].as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(REQUEST_Q_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(REQUEST_Q_EVENT)),
) )
.map_err(DeviceError::EpollCtl)?; .map_err(DeviceError::EpollCtl)?;
epoll::ctl( epoll::ctl(
epoll_fd, epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_ADD, epoll::ControlOptions::EPOLL_CTL_ADD,
self.queue_evts[1].as_raw_fd(), self.queue_evts[1].as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(EVENT_Q_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(EVENT_Q_EVENT)),
) )
.map_err(DeviceError::EpollCtl)?; .map_err(DeviceError::EpollCtl)?;
epoll::ctl( epoll::ctl(
epoll_fd, epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_ADD, epoll::ControlOptions::EPOLL_CTL_ADD,
self.kill_evt.as_raw_fd(), self.kill_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
) )
.map_err(DeviceError::EpollCtl)?; .map_err(DeviceError::EpollCtl)?;
epoll::ctl( epoll::ctl(
epoll_fd, epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_ADD, epoll::ControlOptions::EPOLL_CTL_ADD,
self.pause_evt.as_raw_fd(), self.pause_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)),
@@ -681,7 +688,7 @@ impl IommuEpollHandler {
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
'epoll: loop { 'epoll: loop {
let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) { let num_events = match epoll::wait(epoll_file.as_raw_fd(), -1, &mut events[..]) {
Ok(res) => res, Ok(res) => res,
Err(e) => { Err(e) => {
if e.kind() == io::ErrorKind::Interrupted { if e.kind() == io::ErrorKind::Interrupted {
@@ -753,7 +760,7 @@ impl IommuEpollHandler {
} }
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy, Serialize, Deserialize)]
struct Mapping { struct Mapping {
gpa: u64, gpa: u64,
size: u64, size: u64,
@@ -792,6 +799,7 @@ impl DmaRemapping for IommuMapping {
} }
pub struct Iommu { pub struct Iommu {
id: String,
kill_evt: Option<EventFd>, kill_evt: Option<EventFd>,
pause_evt: Option<EventFd>, pause_evt: Option<EventFd>,
avail_features: u64, avail_features: u64,
@@ -806,8 +814,16 @@ pub struct Iommu {
paused: Arc<AtomicBool>, paused: Arc<AtomicBool>,
} }
#[derive(Serialize, Deserialize)]
struct IommuState {
avail_features: u64,
acked_features: u64,
endpoints: BTreeMap<u32, u32>,
mappings: BTreeMap<u32, BTreeMap<u64, Mapping>>,
}
impl Iommu { impl Iommu {
pub fn new() -> io::Result<(Self, Arc<IommuMapping>)> { pub fn new(id: String) -> io::Result<(Self, Arc<IommuMapping>)> {
let config = VirtioIommuConfig { let config = VirtioIommuConfig {
page_size_mask: VIRTIO_IOMMU_PAGE_SIZE_MASK, page_size_mask: VIRTIO_IOMMU_PAGE_SIZE_MASK,
probe_size: PROBE_PROP_SIZE, probe_size: PROBE_PROP_SIZE,
@@ -821,6 +837,7 @@ impl Iommu {
Ok(( Ok((
Iommu { Iommu {
id,
kill_evt: None, kill_evt: None,
pause_evt: None, pause_evt: None,
avail_features: 1u64 << VIRTIO_F_VERSION_1 avail_features: 1u64 << VIRTIO_F_VERSION_1
@@ -840,6 +857,24 @@ impl Iommu {
)) ))
} }
fn state(&self) -> IommuState {
IommuState {
avail_features: self.avail_features,
acked_features: self.acked_features,
endpoints: self.mapping.endpoints.read().unwrap().clone(),
mappings: self.mapping.mappings.read().unwrap().clone(),
}
}
fn set_state(&mut self, state: &IommuState) -> io::Result<()> {
self.avail_features = state.avail_features;
self.acked_features = state.acked_features;
*(self.mapping.endpoints.write().unwrap()) = state.endpoints.clone();
*(self.mapping.mappings.write().unwrap()) = state.mappings.clone();
Ok(())
}
// This function lets the caller specify a list of devices attached to the // This function lets the caller specify a list of devices attached to the
// virtual IOMMU. This list is translated into a virtio-iommu configuration // virtual IOMMU. This list is translated into a virtio-iommu configuration
// topology, so that it can be understood by the guest driver. // topology, so that it can be understood by the guest driver.
@@ -1038,5 +1073,45 @@ impl VirtioDevice for Iommu {
} }
virtio_pausable!(Iommu); virtio_pausable!(Iommu);
impl Snapshotable for Iommu {} impl Snapshottable for Iommu {
fn id(&self) -> String {
self.id.clone()
}
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
let snapshot =
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
let mut iommu_snapshot = Snapshot::new(self.id.as_str());
iommu_snapshot.add_data_section(SnapshotDataSection {
id: format!("{}-section", self.id),
snapshot,
});
Ok(iommu_snapshot)
}
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
if let Some(iommu_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
let iommu_state = match serde_json::from_slice(&iommu_section.snapshot) {
Ok(state) => state,
Err(error) => {
return Err(MigratableError::Restore(anyhow!(
"Could not deserialize IOMMU {}",
error
)))
}
};
return self.set_state(&iommu_state).map_err(|e| {
MigratableError::Restore(anyhow!("Could not restore IOMMU state {:?}", e))
});
}
Err(MigratableError::Restore(anyhow!(
"Could not find IOMMU snapshot section"
)))
}
}
impl Transportable for Iommu {}
impl Migratable for Iommu {} impl Migratable for Iommu {}

20
vm-virtio/src/lib.rs Executable file → Normal file
View File

@@ -16,6 +16,10 @@ extern crate epoll;
extern crate log; extern crate log;
#[cfg(feature = "pci_support")] #[cfg(feature = "pci_support")]
extern crate pci; extern crate pci;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate vhost_rs; extern crate vhost_rs;
extern crate virtio_bindings; extern crate virtio_bindings;
extern crate vm_device; extern crate vm_device;
@@ -29,6 +33,7 @@ mod device;
pub mod block; pub mod block;
mod console; mod console;
mod iommu; mod iommu;
pub mod mem;
pub mod net; pub mod net;
pub mod net_util; pub mod net_util;
mod pmem; mod pmem;
@@ -43,6 +48,7 @@ pub use self::block::*;
pub use self::console::*; pub use self::console::*;
pub use self::device::*; pub use self::device::*;
pub use self::iommu::*; pub use self::iommu::*;
pub use self::mem::*;
pub use self::net::*; pub use self::net::*;
pub use self::net_util::*; pub use self::net_util::*;
pub use self::pmem::*; pub use self::pmem::*;
@@ -62,11 +68,11 @@ const VIRTIO_F_IOMMU_PLATFORM: u32 = 33;
const VIRTIO_F_IN_ORDER: u32 = 35; const VIRTIO_F_IN_ORDER: u32 = 35;
// Types taken from linux/virtio_ids.h // Types taken from linux/virtio_ids.h
#[derive(Copy, Clone)] #[derive(Copy, Clone, Debug)]
#[allow(dead_code)] #[allow(dead_code)]
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
#[repr(C)] #[repr(C)]
enum VirtioDeviceType { pub enum VirtioDeviceType {
TYPE_NET = 1, TYPE_NET = 1,
TYPE_BLOCK = 2, TYPE_BLOCK = 2,
TYPE_CONSOLE = 3, TYPE_CONSOLE = 3,
@@ -77,6 +83,7 @@ enum VirtioDeviceType {
TYPE_INPUT = 18, TYPE_INPUT = 18,
TYPE_VSOCK = 19, TYPE_VSOCK = 19,
TYPE_IOMMU = 23, TYPE_IOMMU = 23,
TYPE_MEM = 24,
TYPE_FS = 26, TYPE_FS = 26,
TYPE_PMEM = 27, TYPE_PMEM = 27,
TYPE_UNKNOWN = 0xFF, TYPE_UNKNOWN = 0xFF,
@@ -95,6 +102,7 @@ impl From<u32> for VirtioDeviceType {
18 => VirtioDeviceType::TYPE_INPUT, 18 => VirtioDeviceType::TYPE_INPUT,
19 => VirtioDeviceType::TYPE_VSOCK, 19 => VirtioDeviceType::TYPE_VSOCK,
23 => VirtioDeviceType::TYPE_IOMMU, 23 => VirtioDeviceType::TYPE_IOMMU,
24 => VirtioDeviceType::TYPE_MEM,
26 => VirtioDeviceType::TYPE_FS, 26 => VirtioDeviceType::TYPE_FS,
27 => VirtioDeviceType::TYPE_PMEM, 27 => VirtioDeviceType::TYPE_PMEM,
_ => VirtioDeviceType::TYPE_UNKNOWN, _ => VirtioDeviceType::TYPE_UNKNOWN,
@@ -118,6 +126,7 @@ impl fmt::Display for VirtioDeviceType {
VirtioDeviceType::TYPE_INPUT => "input", VirtioDeviceType::TYPE_INPUT => "input",
VirtioDeviceType::TYPE_VSOCK => "vsock", VirtioDeviceType::TYPE_VSOCK => "vsock",
VirtioDeviceType::TYPE_IOMMU => "iommu", VirtioDeviceType::TYPE_IOMMU => "iommu",
VirtioDeviceType::TYPE_MEM => "mem",
VirtioDeviceType::TYPE_FS => "fs", VirtioDeviceType::TYPE_FS => "fs",
VirtioDeviceType::TYPE_PMEM => "pmem", VirtioDeviceType::TYPE_PMEM => "pmem",
VirtioDeviceType::TYPE_UNKNOWN => "UNKNOWN", VirtioDeviceType::TYPE_UNKNOWN => "UNKNOWN",
@@ -171,8 +180,15 @@ pub enum Error {
event: DeviceEventT, event: DeviceEventT,
}, },
IoError(io::Error), IoError(io::Error),
RegisterListener(io::Error),
UnregisterListener(io::Error),
EpollCreateFd(io::Error), EpollCreateFd(io::Error),
EpollCtl(io::Error), EpollCtl(io::Error),
EpollWait(io::Error), EpollWait(io::Error),
FailedSignalingDriver(io::Error), FailedSignalingDriver(io::Error),
VhostUserUpdateMemory(vhost_user::Error),
EventfdError(io::Error),
SetShmRegionsNotSupported,
EpollHander(String),
NoMemoryConfigured,
} }

983
vm-virtio/src/mem.rs Normal file
View File

@@ -0,0 +1,983 @@
// Copyright (c) 2020 Ant Financial
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::Error as DeviceError;
use super::{
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice,
VirtioDeviceType, VIRTIO_F_VERSION_1,
};
use crate::{VirtioInterrupt, VirtioInterruptType};
use libc::EFD_NONBLOCK;
use std::cmp;
use std::fs::File;
use std::io::{self, Write};
use std::mem::size_of;
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::result;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::mpsc;
use std::sync::{Arc, Mutex};
use std::thread;
use vm_memory::{
Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic,
GuestMemoryError, GuestMemoryMmap, GuestMemoryRegion, GuestRegionMmap,
};
use vm_migration::{Migratable, MigratableError, Pausable, Snapshottable, Transportable};
use vmm_sys_util::eventfd::EventFd;
const QUEUE_SIZE: u16 = 128;
const NUM_QUEUES: usize = 1;
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE];
// Use 2 MiB alignment so transparent hugepages can be used by KVM.
pub const VIRTIO_MEM_DEFAULT_BLOCK_SIZE: u64 = 512 * 4096;
const VIRTIO_MEM_USABLE_EXTENT: u64 = 256 * 1024 * 1024;
// Request processed successfully, applicable for
// - VIRTIO_MEM_REQ_PLUG
// - VIRTIO_MEM_REQ_UNPLUG
// - VIRTIO_MEM_REQ_UNPLUG_ALL
// - VIRTIO_MEM_REQ_STATE
const VIRTIO_MEM_RESP_ACK: u16 = 0;
// Request denied - e.g. trying to plug more than requested, applicable for
// - VIRTIO_MEM_REQ_PLUG
const VIRTIO_MEM_RESP_NACK: u16 = 1;
// Request cannot be processed right now, try again later, applicable for
// - VIRTIO_MEM_REQ_PLUG
// - VIRTIO_MEM_REQ_UNPLUG
// - VIRTIO_MEM_REQ_UNPLUG_ALL
// VIRTIO_MEM_RESP_BUSY: u16 = 2;
// Error in request (e.g. addresses/alignemnt), applicable for
// - VIRTIO_MEM_REQ_PLUG
// - VIRTIO_MEM_REQ_UNPLUG
// - VIRTIO_MEM_REQ_STATE
const VIRTIO_MEM_RESP_ERROR: u16 = 3;
// State of memory blocks is "plugged"
const VIRTIO_MEM_STATE_PLUGGED: u16 = 0;
// State of memory blocks is "unplugged"
const VIRTIO_MEM_STATE_UNPLUGGED: u16 = 1;
// State of memory blocks is "mixed"
const VIRTIO_MEM_STATE_MIXED: u16 = 2;
// request to plug memory blocks
const VIRTIO_MEM_REQ_PLUG: u16 = 0;
// request to unplug memory blocks
const VIRTIO_MEM_REQ_UNPLUG: u16 = 1;
// request to unplug all blocks and shrink the usable size
const VIRTIO_MEM_REQ_UNPLUG_ALL: u16 = 2;
// request information about the plugged state of memory blocks
const VIRTIO_MEM_REQ_STATE: u16 = 3;
// Get resize event.
const RESIZE_EVENT: DeviceEventT = 0;
// New descriptors are pending on the virtio queue.
const QUEUE_AVAIL_EVENT: DeviceEventT = 1;
// The device has been dropped.
const KILL_EVENT: DeviceEventT = 2;
// The device should be paused.
const PAUSE_EVENT: DeviceEventT = 3;
#[derive(Debug)]
pub enum Error {
// Guest gave us bad memory addresses.
GuestMemory(GuestMemoryError),
// Guest gave us a write only descriptor that protocol says to read from.
UnexpectedWriteOnlyDescriptor,
// Guest gave us a read only descriptor that protocol says to write to.
UnexpectedReadOnlyDescriptor,
// Guest gave us too few descriptors in a descriptor chain.
DescriptorChainTooShort,
// Guest gave us a buffer that was too short to use.
BufferLengthTooSmall,
// Guest sent us invalid request.
InvalidRequest,
// Failed to EventFd write.
EventFdWriteFail(std::io::Error),
// Failed to EventFd try_clone.
EventFdTryCloneFail(std::io::Error),
// Failed to MpscRecv.
MpscRecvFail(mpsc::RecvError),
// Resize invalid argument
ResizeInval(String),
// Fail to resize trigger
ResizeTriggerFail(DeviceError),
}
// Got from qemu/include/standard-headers/linux/virtio_mem.h
// rust union doesn't support std::default::Default that
// need by mem.read_obj.
// Then move virtio_mem_req_plug, virtio_mem_req_unplug and
// virtio_mem_req_state to virtio_mem_req.
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
struct VirtioMemReq {
req_type: u16,
padding: [u16; 3],
addr: u64,
nb_blocks: u16,
}
// Safe because it only has data and has no implicit padding.
unsafe impl ByteValued for VirtioMemReq {}
// Got from qemu/include/standard-headers/linux/virtio_mem.h
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
struct VirtioMemRespState {
state: u16,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
struct VirtioMemResp {
resp_type: u16,
padding: [u16; 3],
state: VirtioMemRespState,
}
// Safe because it only has data and has no implicit padding.
unsafe impl ByteValued for VirtioMemResp {}
// Got from qemu/include/standard-headers/linux/virtio_mem.h
#[repr(C, packed)]
#[derive(Copy, Clone, Debug, Default)]
struct VirtioMemConfig {
// Block size and alignment. Cannot change.
block_size: u32,
// Valid with VIRTIO_MEM_F_ACPI_PXM. Cannot change.
node_id: u16,
padding: u16,
// Start address of the memory region. Cannot change.
addr: u64,
// Region size (maximum). Cannot change.
region_size: u64,
// Currently usable region size. Can grow up to region_size. Can
// shrink due to VIRTIO_MEM_REQ_UNPLUG_ALL (in which case no config
// update will be sent).
usable_region_size: u64,
// Currently used size. Changes due to plug/unplug requests, but no
// config updates will be sent.
plugged_size: u64,
// Requested size. New plug requests cannot exceed it. Can change.
requested_size: u64,
}
// Safe because it only has data and has no implicit padding.
unsafe impl ByteValued for VirtioMemConfig {}
struct Request {
req: VirtioMemReq,
status_addr: GuestAddress,
}
impl Request {
fn parse(
avail_desc: &DescriptorChain,
mem: &GuestMemoryMmap,
) -> result::Result<Request, Error> {
// The head contains the request type which MUST be readable.
if avail_desc.is_write_only() {
return Err(Error::UnexpectedWriteOnlyDescriptor);
}
if avail_desc.len as usize != size_of::<VirtioMemReq>() {
return Err(Error::InvalidRequest);
}
let req: VirtioMemReq = mem.read_obj(avail_desc.addr).map_err(Error::GuestMemory)?;
let status_desc = avail_desc
.next_descriptor()
.ok_or(Error::DescriptorChainTooShort)?;
// The status MUST always be writable
if !status_desc.is_write_only() {
return Err(Error::UnexpectedReadOnlyDescriptor);
}
if (status_desc.len as usize) < size_of::<VirtioMemResp>() {
return Err(Error::BufferLengthTooSmall);
}
Ok(Request {
req,
status_addr: status_desc.addr,
})
}
}
pub struct Resize {
size: Arc<AtomicU64>,
tx: mpsc::Sender<Result<(), Error>>,
rx: Option<mpsc::Receiver<Result<(), Error>>>,
evt: EventFd,
}
impl Resize {
pub fn new() -> io::Result<Self> {
let (tx, rx) = mpsc::channel();
Ok(Resize {
size: Arc::new(AtomicU64::new(0)),
tx,
rx: Some(rx),
evt: EventFd::new(EFD_NONBLOCK)?,
})
}
pub fn try_clone(&self) -> Result<Self, Error> {
Ok(Resize {
size: self.size.clone(),
tx: self.tx.clone(),
rx: None,
evt: self.evt.try_clone().map_err(Error::EventFdTryCloneFail)?,
})
}
pub fn work(&self, size: u64) -> Result<(), Error> {
if let Some(rx) = &self.rx {
self.size.store(size, Ordering::SeqCst);
self.evt.write(1).map_err(Error::EventFdWriteFail)?;
rx.recv().map_err(Error::MpscRecvFail)?
} else {
panic!("work should not work with cloned resize")
}
}
fn get_size(&self) -> u64 {
self.size.load(Ordering::SeqCst)
}
fn send(&self, r: Result<(), Error>) -> Result<(), mpsc::SendError<Result<(), Error>>> {
self.tx.send(r)
}
}
struct MemEpollHandler {
host_addr: u64,
host_fd: Option<RawFd>,
mem_state: Vec<bool>,
config: Arc<Mutex<VirtioMemConfig>>,
resize: Resize,
queue: Queue,
mem: GuestMemoryAtomic<GuestMemoryMmap>,
interrupt_cb: Arc<dyn VirtioInterrupt>,
queue_evt: EventFd,
kill_evt: EventFd,
pause_evt: EventFd,
}
struct StateChangeRequest<'a> {
config: VirtioMemConfig,
addr: u64,
size: u64,
nb_blocks: u16,
mem_state: &'a mut Vec<bool>,
host_addr: u64,
host_fd: Option<RawFd>,
plug: bool,
}
impl MemEpollHandler {
fn virtio_mem_valid_range(config: &VirtioMemConfig, addr: u64, size: u64) -> bool {
// address properly aligned?
if addr % config.block_size as u64 != 0 {
return false;
}
// reasonable size
if addr + size <= addr || size == 0 {
return false;
}
// start address in usable range?
if addr < config.addr || addr >= config.addr + config.usable_region_size {
return false;
}
// end address in usable range?
if addr + size > config.addr + config.usable_region_size {
return false;
}
true
}
fn virtio_mem_check_bitmap(
bit_index: usize,
nb_blocks: u16,
mem_state: &[bool],
plug: bool,
) -> bool {
for state in mem_state.iter().skip(bit_index).take(nb_blocks as usize) {
if *state != plug {
return false;
}
}
true
}
fn virtio_mem_set_bitmap(
bit_index: usize,
nb_blocks: u16,
mem_state: &mut Vec<bool>,
plug: bool,
) {
for state in mem_state
.iter_mut()
.skip(bit_index)
.take(nb_blocks as usize)
{
*state = plug;
}
}
fn virtio_mem_state_change_request(r: StateChangeRequest) -> u16 {
if r.plug && (r.config.plugged_size + r.size > r.config.requested_size) {
return VIRTIO_MEM_RESP_NACK;
}
if !MemEpollHandler::virtio_mem_valid_range(&r.config, r.addr, r.size) {
return VIRTIO_MEM_RESP_ERROR;
}
let offset = r.addr - r.config.addr;
let bit_index = (offset / r.config.block_size as u64) as usize;
if !MemEpollHandler::virtio_mem_check_bitmap(bit_index, r.nb_blocks, r.mem_state, !r.plug) {
return VIRTIO_MEM_RESP_ERROR;
}
if !r.plug {
if let Some(fd) = r.host_fd {
let res = unsafe {
libc::fallocate64(
fd,
libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_KEEP_SIZE,
offset as libc::off64_t,
r.size as libc::off64_t,
)
};
if res != 0 {
error!("fallocate64 get error {}", io::Error::last_os_error());
return VIRTIO_MEM_RESP_ERROR;
}
}
let res = unsafe {
libc::madvise(
(r.host_addr + offset) as *mut libc::c_void,
r.size as libc::size_t,
libc::MADV_DONTNEED,
)
};
if res != 0 {
error!("madvise get error {}", io::Error::last_os_error());
return VIRTIO_MEM_RESP_ERROR;
}
}
MemEpollHandler::virtio_mem_set_bitmap(bit_index, r.nb_blocks, r.mem_state, r.plug);
VIRTIO_MEM_RESP_ACK
}
fn virtio_mem_unplug_all(
config: VirtioMemConfig,
mem_state: &mut Vec<bool>,
host_addr: u64,
host_fd: Option<RawFd>,
) -> u16 {
for x in 0..(config.region_size / config.block_size as u64) as usize {
if mem_state[x] {
let resp_type =
MemEpollHandler::virtio_mem_state_change_request(StateChangeRequest {
config,
addr: config.addr + x as u64 * config.block_size as u64,
size: config.block_size as u64,
nb_blocks: 1,
mem_state,
host_addr,
host_fd,
plug: false,
});
if resp_type != VIRTIO_MEM_RESP_ACK {
return resp_type;
}
mem_state[x] = false;
}
}
VIRTIO_MEM_RESP_ACK
}
fn virtio_mem_state_request(
config: VirtioMemConfig,
addr: u64,
nb_blocks: u16,
mem_state: &mut Vec<bool>,
) -> (u16, u16) {
let size: u64 = nb_blocks as u64 * config.block_size as u64;
let resp_type = if MemEpollHandler::virtio_mem_valid_range(&config, addr, size) {
VIRTIO_MEM_RESP_ACK
} else {
VIRTIO_MEM_RESP_ERROR
};
let offset = addr - config.addr;
let bit_index = (offset / config.block_size as u64) as usize;
let resp_state =
if MemEpollHandler::virtio_mem_check_bitmap(bit_index, nb_blocks, mem_state, true) {
VIRTIO_MEM_STATE_PLUGGED
} else if MemEpollHandler::virtio_mem_check_bitmap(
bit_index, nb_blocks, mem_state, false,
) {
VIRTIO_MEM_STATE_UNPLUGGED
} else {
VIRTIO_MEM_STATE_MIXED
};
(resp_type, resp_state)
}
fn virtio_mem_send_response(
mem: &GuestMemoryMmap,
resp_type: u16,
resp_state: u16,
status_addr: GuestAddress,
) -> u32 {
let mut resp = VirtioMemResp::default();
resp.resp_type = resp_type;
resp.state.state = resp_state;
match mem.write_obj(resp, status_addr) {
Ok(_) => size_of::<VirtioMemResp>() as u32,
Err(e) => {
error!("bad guest memory address: {}", e);
0
}
}
}
fn signal(&self, int_type: &VirtioInterruptType) -> result::Result<(), DeviceError> {
self.interrupt_cb
.trigger(int_type, Some(&self.queue))
.map_err(|e| {
error!("Failed to signal used queue: {:?}", e);
DeviceError::FailedSignalingUsedQueue(e)
})
}
fn process_queue(&mut self) -> bool {
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
let mut used_count = 0;
let mem = self.mem.memory();
for avail_desc in self.queue.iter(&mem) {
let len = match Request::parse(&avail_desc, &mem) {
Err(e) => {
error!("failed parse VirtioMemReq: {:?}", e);
0
}
Ok(r) => {
let mut config = self.config.lock().unwrap();
match r.req.req_type {
VIRTIO_MEM_REQ_PLUG => {
let size: u64 = r.req.nb_blocks as u64 * config.block_size as u64;
let resp_type = MemEpollHandler::virtio_mem_state_change_request(
StateChangeRequest {
config: *config,
addr: r.req.addr,
size,
nb_blocks: r.req.nb_blocks,
mem_state: &mut self.mem_state,
host_addr: self.host_addr,
host_fd: self.host_fd,
plug: true,
},
);
if resp_type == VIRTIO_MEM_RESP_ACK {
config.plugged_size += size;
}
MemEpollHandler::virtio_mem_send_response(
&mem,
resp_type,
0u16,
r.status_addr,
)
}
VIRTIO_MEM_REQ_UNPLUG => {
let size: u64 = r.req.nb_blocks as u64 * config.block_size as u64;
let resp_type = MemEpollHandler::virtio_mem_state_change_request(
StateChangeRequest {
config: *config,
addr: r.req.addr,
size,
nb_blocks: r.req.nb_blocks,
mem_state: &mut self.mem_state,
host_addr: self.host_addr,
host_fd: self.host_fd,
plug: false,
},
);
if resp_type == VIRTIO_MEM_RESP_ACK {
config.plugged_size -= size;
}
MemEpollHandler::virtio_mem_send_response(
&mem,
resp_type,
0u16,
r.status_addr,
)
}
VIRTIO_MEM_REQ_UNPLUG_ALL => {
let resp_type = MemEpollHandler::virtio_mem_unplug_all(
*config,
&mut self.mem_state,
self.host_addr,
self.host_fd,
);
if resp_type == VIRTIO_MEM_RESP_ACK {
config.plugged_size = 0;
config.usable_region_size = cmp::min(
config.region_size,
config.requested_size + VIRTIO_MEM_USABLE_EXTENT,
);
}
MemEpollHandler::virtio_mem_send_response(
&mem,
resp_type,
0u16,
r.status_addr,
)
}
VIRTIO_MEM_REQ_STATE => {
let (resp_type, resp_state) = MemEpollHandler::virtio_mem_state_request(
*config,
r.req.addr,
r.req.nb_blocks,
&mut self.mem_state,
);
MemEpollHandler::virtio_mem_send_response(
&mem,
resp_type,
resp_state,
r.status_addr,
)
}
_ => {
error!("VirtioMemReq unknown request type {:?}", r.req.req_type);
0
}
}
}
};
used_desc_heads[used_count] = (avail_desc.index, len);
used_count += 1;
}
for &(desc_index, len) in &used_desc_heads[..used_count] {
self.queue.add_used(&mem, desc_index, len);
}
used_count > 0
}
fn run(&mut self, paused: Arc<AtomicBool>) -> result::Result<(), DeviceError> {
// Create the epoll file descriptor
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
// Use 'File' to enforce closing on 'epoll_fd'
let epoll_file = unsafe { File::from_raw_fd(epoll_fd) };
// Add events
epoll::ctl(
epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_ADD,
self.resize.evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RESIZE_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
epoll::ctl(
epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_ADD,
self.queue_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(QUEUE_AVAIL_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
epoll::ctl(
epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_ADD,
self.kill_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
epoll::ctl(
epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_ADD,
self.pause_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
const EPOLL_EVENTS_LEN: usize = 100;
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
'epoll: loop {
let num_events = match epoll::wait(epoll_file.as_raw_fd(), -1, &mut events[..]) {
Ok(res) => res,
Err(e) => {
if e.kind() == io::ErrorKind::Interrupted {
// It's well defined from the epoll_wait() syscall
// documentation that the epoll loop can be interrupted
// before any of the requested events occurred or the
// timeout expired. In both those cases, epoll_wait()
// returns an error of type EINTR, but this should not
// be considered as a regular error. Instead it is more
// appropriate to retry, by calling into epoll_wait().
continue;
}
return Err(DeviceError::EpollWait(e));
}
};
for event in events.iter().take(num_events) {
let ev_type = event.data as u16;
match ev_type {
RESIZE_EVENT => {
if let Err(e) = self.resize.evt.read() {
return Err(DeviceError::EpollHander(format!(
"Failed to get resize event: {:?}",
e
)));
} else {
let size = self.resize.get_size();
let mut config = self.config.lock().unwrap();
let mut signal_error = false;
let r = if config.requested_size == size {
Err(Error::ResizeInval(format!("Virtio-mem resize {} is same with current config.requested_size", size)))
} else if size > config.region_size {
let region_size = config.region_size;
Err(Error::ResizeInval(format!(
"Virtio-mem resize {} is bigger than config.region_size {}",
size, region_size
)))
} else if size % (config.block_size as u64) != 0 {
let block_size = config.block_size;
Err(Error::ResizeInval(format!(
"Virtio-mem resize {} is not aligned with config.block_size {}",
size, block_size
)))
} else {
config.requested_size = size;
let tmp_size = cmp::min(
config.region_size,
config.requested_size + VIRTIO_MEM_USABLE_EXTENT,
);
config.usable_region_size =
cmp::max(config.usable_region_size, tmp_size);
if let Err(e) = self.signal(&VirtioInterruptType::Config) {
signal_error = true;
Err(Error::ResizeTriggerFail(e))
} else {
Ok(())
}
};
if let Err(e) = &r {
// This error will send back to resize caller.
error!("Handle resize event get error: {:?}", e);
}
if let Err(e) = self.resize.send(r) {
return Err(DeviceError::EpollHander(format!(
"Sending \"resize\" generated error: {:?}",
e
)));
}
if signal_error {
return Err(DeviceError::EpollHander(String::from(
"Signal get error",
)));
}
}
}
QUEUE_AVAIL_EVENT => {
if let Err(e) = self.queue_evt.read() {
return Err(DeviceError::EpollHander(format!(
"Failed to get queue event: {:?}",
e
)));
} else if self.process_queue() {
if let Err(e) = self.signal(&VirtioInterruptType::Queue) {
return Err(DeviceError::EpollHander(format!(
"Failed to signal used queue: {:?}",
e
)));
}
}
}
KILL_EVENT => {
debug!("kill_evt received, stopping epoll loop");
break 'epoll;
}
PAUSE_EVENT => {
debug!("PAUSE_EVENT received, pausing virtio-pmem epoll loop");
// We loop here to handle spurious park() returns.
// Until we have not resumed, the paused boolean will
// be true.
while paused.load(Ordering::SeqCst) {
thread::park();
}
}
_ => {
return Err(DeviceError::EpollHander(String::from(
"Unknown event for virtio-mem",
)));
}
}
}
}
Ok(())
}
}
// Virtio device for exposing entropy to the guest OS through virtio.
pub struct Mem {
id: String,
resize: Resize,
kill_evt: Option<EventFd>,
pause_evt: Option<EventFd>,
avail_features: u64,
pub acked_features: u64,
host_addr: u64,
host_fd: Option<RawFd>,
config: Arc<Mutex<VirtioMemConfig>>,
queue_evts: Option<Vec<EventFd>>,
interrupt_cb: Option<Arc<dyn VirtioInterrupt>>,
epoll_threads: Option<Vec<thread::JoinHandle<result::Result<(), DeviceError>>>>,
paused: Arc<AtomicBool>,
}
impl Mem {
// Create a new virtio-mem device.
pub fn new(id: String, region: &Arc<GuestRegionMmap>, resize: Resize) -> io::Result<Mem> {
let region_len = region.len();
if region_len != region_len / VIRTIO_MEM_DEFAULT_BLOCK_SIZE * VIRTIO_MEM_DEFAULT_BLOCK_SIZE
{
return Err(io::Error::new(
io::ErrorKind::Other,
format!(
"Virtio-mem size is not aligned with {}",
VIRTIO_MEM_DEFAULT_BLOCK_SIZE
),
));
}
// Fixme: Not support VIRTIO_MEM_F_ACPI_PXM
let avail_features = 1u64 << VIRTIO_F_VERSION_1;
let mut config = VirtioMemConfig::default();
config.block_size = VIRTIO_MEM_DEFAULT_BLOCK_SIZE as u32;
config.addr = region.start_addr().raw_value();
config.region_size = region.len();
config.usable_region_size = cmp::min(
config.region_size,
config.requested_size + VIRTIO_MEM_USABLE_EXTENT,
);
let host_fd = if let Some(f_offset) = region.file_offset() {
Some(f_offset.file().as_raw_fd())
} else {
None
};
Ok(Mem {
id,
resize,
kill_evt: None,
pause_evt: None,
avail_features,
acked_features: 0u64,
host_addr: region.as_ptr() as u64,
host_fd,
config: Arc::new(Mutex::new(config)),
queue_evts: None,
interrupt_cb: None,
epoll_threads: None,
paused: Arc::new(AtomicBool::new(false)),
})
}
}
impl Drop for Mem {
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 Mem {
fn device_type(&self) -> u32 {
VirtioDeviceType::TYPE_MEM as u32
}
fn queue_max_sizes(&self) -> &[u16] {
QUEUE_SIZES
}
fn features(&self) -> u64 {
self.avail_features
}
fn ack_features(&mut self, value: u64) {
let mut v = value;
// Check if the guest is ACK'ing a feature that we didn't claim to have.
let unrequested_features = v & !self.avail_features;
if unrequested_features != 0 {
warn!("Received acknowledge request for unknown feature.");
// Don't count these features as acked.
v &= !unrequested_features;
}
self.acked_features |= v;
}
fn read_config(&self, offset: u64, mut data: &mut [u8]) {
let config = 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!("virtio-mem device configuration is read-only");
}
fn activate(
&mut self,
mem: GuestMemoryAtomic<GuestMemoryMmap>,
interrupt_cb: Arc<dyn VirtioInterrupt>,
mut queues: Vec<Queue>,
mut queue_evts: Vec<EventFd>,
) -> ActivateResult {
if queues.len() != NUM_QUEUES || queue_evts.len() != NUM_QUEUES {
error!(
"Cannot perform activate. Expected {} queue(s), got {}",
NUM_QUEUES,
queues.len()
);
return Err(ActivateError::BadActivate);
}
let (self_kill_evt, kill_evt) = EventFd::new(EFD_NONBLOCK)
.and_then(|e| Ok((e.try_clone()?, e)))
.map_err(|e| {
error!("failed creating kill EventFd pair: {}", e);
ActivateError::BadActivate
})?;
self.kill_evt = Some(self_kill_evt);
let (self_pause_evt, pause_evt) = EventFd::new(EFD_NONBLOCK)
.and_then(|e| Ok((e.try_clone()?, e)))
.map_err(|e| {
error!("failed creating pause EventFd pair: {}", e);
ActivateError::BadActivate
})?;
self.pause_evt = Some(self_pause_evt);
self.interrupt_cb = Some(interrupt_cb.clone());
let mut tmp_queue_evts: Vec<EventFd> = Vec::new();
for queue_evt in queue_evts.iter() {
// Save the queue EventFD as we need to return it on reset
// but clone it to pass into the thread.
tmp_queue_evts.push(queue_evt.try_clone().map_err(|e| {
error!("failed to clone queue EventFd: {}", e);
ActivateError::BadActivate
})?);
}
self.queue_evts = Some(tmp_queue_evts);
let config = self.config.lock().unwrap();
let mut handler = MemEpollHandler {
host_addr: self.host_addr,
host_fd: self.host_fd,
mem_state: vec![false; config.region_size as usize / config.block_size as usize],
config: self.config.clone(),
resize: self.resize.try_clone().map_err(|e| {
error!("failed to clone resize EventFd: {:?}", e);
ActivateError::BadActivate
})?,
queue: queues.remove(0),
mem,
interrupt_cb,
queue_evt: queue_evts.remove(0),
kill_evt,
pause_evt,
};
let paused = self.paused.clone();
let mut epoll_threads = Vec::new();
thread::Builder::new()
.name("virtio_mem".to_string())
.spawn(move || handler.run(paused))
.map(|thread| epoll_threads.push(thread))
.map_err(|e| {
error!("failed to clone virtio-mem epoll thread: {}", e);
ActivateError::BadActivate
})?;
self.epoll_threads = Some(epoll_threads);
Ok(())
}
fn reset(&mut self) -> Option<(Arc<dyn VirtioInterrupt>, Vec<EventFd>)> {
// We first must resume the virtio thread if it was paused.
if self.pause_evt.take().is_some() {
self.resume().ok()?;
}
if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1);
}
// Return the interrupt and queue EventFDs
Some((
self.interrupt_cb.take().unwrap(),
self.queue_evts.take().unwrap(),
))
}
}
virtio_pausable!(Mem);
impl Snapshottable for Mem {
fn id(&self) -> String {
self.id.clone()
}
}
impl Transportable for Mem {}
impl Migratable for Mem {}

View File

@@ -15,23 +15,28 @@ use super::{
ActivateError, ActivateResult, Queue, VirtioDevice, VirtioDeviceType, VirtioInterruptType, ActivateError, ActivateResult, Queue, VirtioDevice, VirtioDeviceType, VirtioInterruptType,
}; };
use crate::VirtioInterrupt; use crate::VirtioInterrupt;
use epoll; use anyhow::anyhow;
use libc::EAGAIN; use libc::EAGAIN;
use libc::EFD_NONBLOCK; use libc::EFD_NONBLOCK;
use net_util::{MacAddr, Tap}; use net_util::{MacAddr, Tap};
use std::cmp; use std::cmp;
use std::fs::File;
use std::io::Read; use std::io::Read;
use std::io::{self, Write}; use std::io::{self, Write};
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::result; use std::result;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::thread;
use std::vec::Vec; use std::vec::Vec;
use virtio_bindings::bindings::virtio_net::*; use virtio_bindings::bindings::virtio_net::*;
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable}; use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
use vm_memory::{ByteValued, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap}; use vm_memory::{ByteValued, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
Transportable,
};
use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::eventfd::EventFd;
#[derive(Debug)] #[derive(Debug)]
@@ -42,60 +47,53 @@ pub enum Error {
pub type Result<T> = result::Result<T, Error>; pub type Result<T> = result::Result<T, Error>;
struct NetEpollHandler { pub struct NetQueuePair {
mem: GuestMemoryAtomic<GuestMemoryMmap>, pub mem: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
tap: Tap, pub tap: Tap,
rx: RxVirtio, pub rx: RxVirtio,
tx: TxVirtio, pub tx: TxVirtio,
interrupt_cb: Arc<dyn VirtioInterrupt>, pub epoll_fd: Option<RawFd>,
kill_evt: EventFd, pub rx_tap_listening: bool,
pause_evt: EventFd,
epoll_fd: RawFd,
rx_tap_listening: bool,
} }
impl NetEpollHandler { impl NetQueuePair {
fn signal_used_queue(&self, queue: &Queue) -> result::Result<(), DeviceError> {
self.interrupt_cb
.trigger(&VirtioInterruptType::Queue, Some(queue))
.map_err(|e| {
error!("Failed to signal used queue: {:?}", e);
DeviceError::FailedSignalingUsedQueue(e)
})
}
// Copies a single frame from `self.rx.frame_buf` into the guest. Returns true // Copies a single frame from `self.rx.frame_buf` into the guest. Returns true
// if a buffer was used, and false if the frame must be deferred until a buffer // if a buffer was used, and false if the frame must be deferred until a buffer
// is made available by the driver. // is made available by the driver.
fn rx_single_frame(&mut self, mut queue: &mut Queue) -> bool { fn rx_single_frame(&mut self, mut queue: &mut Queue) -> result::Result<bool, DeviceError> {
let mem = self.mem.memory(); let mem = self
.mem
.as_ref()
.ok_or(DeviceError::NoMemoryConfigured)
.map(|m| m.memory())?;
let next_desc = queue.iter(&mem).next(); let next_desc = queue.iter(&mem).next();
if next_desc.is_none() { if next_desc.is_none() {
// Queue has no available descriptors // Queue has no available descriptors
if self.rx_tap_listening { if self.rx_tap_listening {
unregister_listener( unregister_listener(
self.epoll_fd, self.epoll_fd.unwrap(),
self.tap.as_raw_fd(), self.tap.as_raw_fd(),
epoll::Events::EPOLLIN, epoll::Events::EPOLLIN,
u64::from(RX_TAP_EVENT), u64::from(RX_TAP_EVENT),
) )
.unwrap(); .map_err(DeviceError::UnregisterListener)?;
self.rx_tap_listening = false; self.rx_tap_listening = false;
info!("Listener unregistered");
} }
return false; return Ok(false);
} }
self.rx.process_desc_chain(&mem, next_desc, &mut queue) Ok(self.rx.process_desc_chain(&mem, next_desc, &mut queue))
} }
fn process_rx(&mut self, queue: &mut Queue) -> result::Result<(), DeviceError> { fn process_rx(&mut self, queue: &mut Queue) -> result::Result<bool, DeviceError> {
// Read as many frames as possible. // Read as many frames as possible.
loop { loop {
match self.read_tap() { match self.read_tap() {
Ok(count) => { Ok(count) => {
self.rx.bytes_read = count; self.rx.bytes_read = count;
if !self.rx_single_frame(queue) { if !self.rx_single_frame(queue)? {
self.rx.deferred_frame = true; self.rx.deferred_frame = true;
break; break;
} }
@@ -116,83 +114,152 @@ impl NetEpollHandler {
} }
if self.rx.deferred_irqs { if self.rx.deferred_irqs {
self.rx.deferred_irqs = false; self.rx.deferred_irqs = false;
self.signal_used_queue(queue) let mem = self
.mem
.as_ref()
.ok_or(DeviceError::NoMemoryConfigured)
.map(|m| m.memory())?;
Ok(queue.needs_notification(&mem, queue.next_used))
} else { } else {
Ok(()) Ok(false)
} }
} }
fn resume_rx(&mut self, queue: &mut Queue) -> result::Result<(), DeviceError> { pub fn resume_rx(&mut self, queue: &mut Queue) -> result::Result<bool, DeviceError> {
if !self.rx_tap_listening {
register_listener(
self.epoll_fd.unwrap(),
self.tap.as_raw_fd(),
epoll::Events::EPOLLIN,
u64::from(RX_TAP_EVENT),
)
.map_err(DeviceError::RegisterListener)?;
self.rx_tap_listening = true;
info!("Listener registered");
}
if self.rx.deferred_frame { if self.rx.deferred_frame {
if self.rx_single_frame(queue) { if self.rx_single_frame(queue)? {
self.rx.deferred_frame = false; self.rx.deferred_frame = false;
// process_rx() was interrupted possibly before consuming all // process_rx() was interrupted possibly before consuming all
// packets in the tap; try continuing now. // packets in the tap; try continuing now.
self.process_rx(queue) self.process_rx(queue)
} else if self.rx.deferred_irqs { } else if self.rx.deferred_irqs {
self.rx.deferred_irqs = false; self.rx.deferred_irqs = false;
self.signal_used_queue(queue) let mem = self
.mem
.as_ref()
.ok_or(DeviceError::NoMemoryConfigured)
.map(|m| m.memory())?;
Ok(queue.needs_notification(&mem, queue.next_used))
} else { } else {
Ok(()) Ok(false)
} }
} else { } else {
Ok(()) Ok(false)
} }
} }
fn process_tx(&mut self, mut queue: &mut Queue) -> result::Result<(), DeviceError> { pub fn process_tx(&mut self, mut queue: &mut Queue) -> result::Result<bool, DeviceError> {
let mem = self.mem.memory(); let mem = self
.mem
.as_ref()
.ok_or(DeviceError::NoMemoryConfigured)
.map(|m| m.memory())?;
self.tx.process_desc_chain(&mem, &mut self.tap, &mut queue); self.tx.process_desc_chain(&mem, &mut self.tap, &mut queue);
Ok(queue.needs_notification(&mem, queue.next_used))
}
Ok(()) pub fn process_rx_tap(&mut self, mut queue: &mut Queue) -> result::Result<bool, DeviceError> {
if self.rx.deferred_frame
// Process a deferred frame first if available. Don't read from tap again
// until we manage to receive this deferred frame.
{
if self.rx_single_frame(&mut queue)? {
self.rx.deferred_frame = false;
self.process_rx(&mut queue)
} else if self.rx.deferred_irqs {
self.rx.deferred_irqs = false;
Ok(true)
} else {
Ok(false)
}
} else {
self.process_rx(&mut queue)
}
} }
fn read_tap(&mut self) -> io::Result<usize> { fn read_tap(&mut self) -> io::Result<usize> {
self.tap.read(&mut self.rx.frame_buf) self.tap.read(&mut self.rx.frame_buf)
} }
}
fn handle_rx_event(&mut self, mut queue: &mut Queue, queue_evt: &EventFd) { struct NetEpollHandler {
net: NetQueuePair,
interrupt_cb: Arc<dyn VirtioInterrupt>,
kill_evt: EventFd,
pause_evt: EventFd,
// Always generate interrupts until the driver has signalled to the device.
// This mitigates a problem with interrupts from tap events being "lost" upon
// a restore as the vCPU thread isn't ready to handle the interrupt. This causes
// issues when combined with VIRTIO_RING_F_EVENT_IDX interrupt suppression.
driver_awake: bool,
}
impl NetEpollHandler {
fn signal_used_queue(&self, queue: &Queue) -> result::Result<(), DeviceError> {
self.interrupt_cb
.trigger(&VirtioInterruptType::Queue, Some(queue))
.map_err(|e| {
error!("Failed to signal used queue: {:?}", e);
DeviceError::FailedSignalingUsedQueue(e)
})
}
fn handle_rx_event(
&mut self,
mut queue: &mut Queue,
queue_evt: &EventFd,
) -> result::Result<(), DeviceError> {
if let Err(e) = queue_evt.read() { if let Err(e) = queue_evt.read() {
error!("Failed to get rx queue event: {:?}", e); error!("Failed to get rx queue event: {:?}", e);
} }
self.resume_rx(&mut queue).unwrap(); if self.net.resume_rx(&mut queue)? || !self.driver_awake {
if !self.rx_tap_listening { self.signal_used_queue(queue)?;
register_listener( info!("Signalling RX queue");
self.epoll_fd, } else {
self.tap.as_raw_fd(), info!("Not signalling RX queue");
epoll::Events::EPOLLIN,
u64::from(RX_TAP_EVENT),
)
.unwrap();
self.rx_tap_listening = true;
} }
Ok(())
} }
fn handle_tx_event(&mut self, mut queue: &mut Queue, queue_evt: &EventFd) { fn handle_tx_event(
&mut self,
mut queue: &mut Queue,
queue_evt: &EventFd,
) -> result::Result<(), DeviceError> {
if let Err(e) = queue_evt.read() { if let Err(e) = queue_evt.read() {
error!("Failed to get tx queue event: {:?}", e); error!("Failed to get tx queue event: {:?}", e);
} }
if self.net.process_tx(&mut queue)? || !self.driver_awake {
self.process_tx(&mut queue).unwrap(); self.signal_used_queue(queue)?;
info!("Signalling TX queue");
} else {
info!("Not signalling TX queue");
}
Ok(())
} }
fn handle_rx_tap_event(&mut self, mut queue: &mut Queue) { fn handle_rx_tap_event(&mut self, queue: &mut Queue) -> result::Result<(), DeviceError> {
if self.rx.deferred_frame if self.net.process_rx_tap(queue)? || !self.driver_awake {
// Process a deferred frame first if available. Don't read from tap again self.signal_used_queue(queue)?;
// until we manage to receive this deferred frame. info!("Signalling RX queue");
{
if self.rx_single_frame(&mut queue) {
self.rx.deferred_frame = false;
self.process_rx(&mut queue).unwrap();
} else if self.rx.deferred_irqs {
self.rx.deferred_irqs = false;
self.signal_used_queue(&queue).unwrap();
}
} else { } else {
self.process_rx(&mut queue).unwrap(); info!("Not signalling RX queue");
} }
Ok(())
} }
fn run( fn run(
@@ -202,42 +269,62 @@ impl NetEpollHandler {
queue_evts: Vec<EventFd>, queue_evts: Vec<EventFd>,
) -> result::Result<(), DeviceError> { ) -> result::Result<(), DeviceError> {
// Create the epoll file descriptor // Create the epoll file descriptor
self.epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?; let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
// Add events // Use 'File' to enforce closing on 'epoll_fd'
let epoll_file = unsafe { File::from_raw_fd(epoll_fd) };
self.net.epoll_fd = Some(epoll_fd);
// Add events // Add events
epoll::ctl( epoll::ctl(
self.epoll_fd, epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_ADD, epoll::ControlOptions::EPOLL_CTL_ADD,
queue_evts[0].as_raw_fd(), queue_evts[0].as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_QUEUE_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_QUEUE_EVENT)),
) )
.map_err(DeviceError::EpollCtl)?; .map_err(DeviceError::EpollCtl)?;
epoll::ctl( epoll::ctl(
self.epoll_fd, epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_ADD, epoll::ControlOptions::EPOLL_CTL_ADD,
queue_evts[1].as_raw_fd(), queue_evts[1].as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(TX_QUEUE_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(TX_QUEUE_EVENT)),
) )
.map_err(DeviceError::EpollCtl)?; .map_err(DeviceError::EpollCtl)?;
epoll::ctl( epoll::ctl(
self.epoll_fd, epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_ADD, epoll::ControlOptions::EPOLL_CTL_ADD,
self.kill_evt.as_raw_fd(), self.kill_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
) )
.map_err(DeviceError::EpollCtl)?; .map_err(DeviceError::EpollCtl)?;
epoll::ctl( epoll::ctl(
self.epoll_fd, epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_ADD, epoll::ControlOptions::EPOLL_CTL_ADD,
self.pause_evt.as_raw_fd(), self.pause_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)),
) )
.map_err(DeviceError::EpollCtl)?; .map_err(DeviceError::EpollCtl)?;
// If there are some already available descriptors on the RX queue,
// then we can start the thread while listening onto the TAP.
if queues[0]
.available_descriptors(&self.net.mem.as_ref().unwrap().memory())
.unwrap()
{
epoll::ctl(
epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_ADD,
self.net.tap.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_TAP_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
self.net.rx_tap_listening = true;
error!("Listener registered at start");
}
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); NET_EVENTS_COUNT]; let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); NET_EVENTS_COUNT];
'epoll: loop { 'epoll: loop {
let num_events = match epoll::wait(self.epoll_fd, -1, &mut events[..]) { let num_events = match epoll::wait(epoll_file.as_raw_fd(), -1, &mut events[..]) {
Ok(res) => res, Ok(res) => res,
Err(e) => { Err(e) => {
if e.kind() == io::ErrorKind::Interrupted { if e.kind() == io::ErrorKind::Interrupted {
@@ -259,13 +346,15 @@ impl NetEpollHandler {
match ev_type { match ev_type {
RX_QUEUE_EVENT => { RX_QUEUE_EVENT => {
self.handle_rx_event(&mut queues[0], &queue_evts[0]); self.driver_awake = true;
self.handle_rx_event(&mut queues[0], &queue_evts[0])?;
} }
TX_QUEUE_EVENT => { TX_QUEUE_EVENT => {
self.handle_tx_event(&mut queues[1], &queue_evts[1]); self.driver_awake = true;
self.handle_tx_event(&mut queues[1], &queue_evts[1])?;
} }
RX_TAP_EVENT => { RX_TAP_EVENT => {
self.handle_rx_tap_event(&mut queues[0]); self.handle_rx_tap_event(&mut queues[0])?;
} }
KILL_EVENT => { KILL_EVENT => {
debug!("KILL_EVENT received, stopping epoll loop"); debug!("KILL_EVENT received, stopping epoll loop");
@@ -275,6 +364,7 @@ impl NetEpollHandler {
// Drain pause event // Drain pause event
let _ = self.pause_evt.read(); let _ = self.pause_evt.read();
debug!("PAUSE_EVENT received, pausing virtio-net epoll loop"); debug!("PAUSE_EVENT received, pausing virtio-net epoll loop");
// We loop here to handle spurious park() returns. // We loop here to handle spurious park() returns.
// Until we have not resumed, the paused boolean will // Until we have not resumed, the paused boolean will
// be true. // be true.
@@ -293,6 +383,7 @@ impl NetEpollHandler {
} }
pub struct Net { pub struct Net {
id: String,
kill_evt: Option<EventFd>, kill_evt: Option<EventFd>,
pause_evt: Option<EventFd>, pause_evt: Option<EventFd>,
taps: Option<Vec<Tap>>, taps: Option<Vec<Tap>>,
@@ -307,9 +398,18 @@ pub struct Net {
queue_size: Vec<u16>, queue_size: Vec<u16>,
} }
#[derive(Serialize, Deserialize)]
pub struct NetState {
pub avail_features: u64,
pub acked_features: u64,
pub config: VirtioNetConfig,
pub queue_size: Vec<u16>,
}
impl Net { impl Net {
/// Create a new virtio network device with the given TAP interface. /// Create a new virtio network device with the given TAP interface.
pub fn new_with_tap( pub fn new_with_tap(
id: String,
taps: Vec<Tap>, taps: Vec<Tap>,
guest_mac: Option<MacAddr>, guest_mac: Option<MacAddr>,
iommu: bool, iommu: bool,
@@ -322,6 +422,7 @@ impl Net {
| 1 << VIRTIO_NET_F_GUEST_UFO | 1 << VIRTIO_NET_F_GUEST_UFO
| 1 << VIRTIO_NET_F_HOST_TSO4 | 1 << VIRTIO_NET_F_HOST_TSO4
| 1 << VIRTIO_NET_F_HOST_UFO | 1 << VIRTIO_NET_F_HOST_UFO
| 1 << VIRTIO_RING_F_EVENT_IDX
| 1 << VIRTIO_F_VERSION_1; | 1 << VIRTIO_F_VERSION_1;
if iommu { if iommu {
@@ -339,6 +440,7 @@ impl Net {
} }
Ok(Net { Ok(Net {
id,
kill_evt: None, kill_evt: None,
pause_evt: None, pause_evt: None,
taps: Some(taps), taps: Some(taps),
@@ -356,18 +458,40 @@ impl Net {
/// Create a new virtio network device with the given IP address and /// Create a new virtio network device with the given IP address and
/// netmask. /// netmask.
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
id: String,
if_name: Option<&str>, if_name: Option<&str>,
ip_addr: Option<Ipv4Addr>, ip_addr: Option<Ipv4Addr>,
netmask: Option<Ipv4Addr>, netmask: Option<Ipv4Addr>,
guest_mac: Option<MacAddr>, guest_mac: Option<MacAddr>,
host_mac: &mut Option<MacAddr>,
iommu: bool, iommu: bool,
num_queues: usize, num_queues: usize,
queue_size: u16, queue_size: u16,
) -> Result<Self> { ) -> Result<Self> {
let taps = open_tap(if_name, ip_addr, netmask, num_queues / 2).map_err(Error::OpenTap)?; let taps = open_tap(if_name, ip_addr, netmask, host_mac, num_queues / 2)
.map_err(Error::OpenTap)?;
Self::new_with_tap(taps, guest_mac, iommu, num_queues, queue_size) Self::new_with_tap(id, taps, guest_mac, iommu, num_queues, queue_size)
}
fn state(&self) -> NetState {
NetState {
avail_features: self.avail_features,
acked_features: self.acked_features,
config: self.config,
queue_size: self.queue_size.clone(),
}
}
fn set_state(&mut self, state: &NetState) -> Result<()> {
self.avail_features = state.avail_features;
self.acked_features = state.acked_features;
self.config = state.config;
self.queue_size = state.queue_size.clone();
Ok(())
} }
} }
@@ -503,6 +627,8 @@ impl VirtioDevice for Net {
})?; })?;
} }
let event_idx = self.acked_features & 1 << VIRTIO_RING_F_EVENT_IDX != 0;
let mut epoll_threads = Vec::new(); let mut epoll_threads = Vec::new();
for _ in 0..taps.len() { for _ in 0..taps.len() {
let rx = RxVirtio::new(); let rx = RxVirtio::new();
@@ -512,21 +638,26 @@ impl VirtioDevice for Net {
let mut queue_pair = Vec::new(); let mut queue_pair = Vec::new();
queue_pair.push(queues.remove(0)); queue_pair.push(queues.remove(0));
queue_pair.push(queues.remove(0)); queue_pair.push(queues.remove(0));
queue_pair[0].set_event_idx(event_idx);
queue_pair[1].set_event_idx(event_idx);
let mut queue_evt_pair = Vec::new(); let mut queue_evt_pair = Vec::new();
queue_evt_pair.push(queue_evts.remove(0)); queue_evt_pair.push(queue_evts.remove(0));
queue_evt_pair.push(queue_evts.remove(0)); queue_evt_pair.push(queue_evts.remove(0));
let mut handler = NetEpollHandler { let mut handler = NetEpollHandler {
mem: mem.clone(), net: NetQueuePair {
tap: taps.remove(0), mem: Some(mem.clone()),
rx, tap: taps.remove(0),
tx, rx,
tx,
epoll_fd: None,
rx_tap_listening,
},
interrupt_cb: interrupt_cb.clone(), interrupt_cb: interrupt_cb.clone(),
kill_evt: kill_evt.try_clone().unwrap(), kill_evt: kill_evt.try_clone().unwrap(),
pause_evt: pause_evt.try_clone().unwrap(), pause_evt: pause_evt.try_clone().unwrap(),
epoll_fd: 0, driver_awake: false,
rx_tap_listening,
}; };
let paused = self.paused.clone(); let paused = self.paused.clone();
@@ -567,5 +698,45 @@ impl VirtioDevice for Net {
} }
virtio_ctrl_q_pausable!(Net); virtio_ctrl_q_pausable!(Net);
impl Snapshotable for Net {} impl Snapshottable for Net {
fn id(&self) -> String {
self.id.clone()
}
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
let snapshot =
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
let mut net_snapshot = Snapshot::new(self.id.as_str());
net_snapshot.add_data_section(SnapshotDataSection {
id: format!("{}-section", self.id),
snapshot,
});
Ok(net_snapshot)
}
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
if let Some(net_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
let net_state = match serde_json::from_slice(&net_section.snapshot) {
Ok(state) => state,
Err(error) => {
return Err(MigratableError::Restore(anyhow!(
"Could not deserialize NET {}",
error
)))
}
};
return self.set_state(&net_state).map_err(|e| {
MigratableError::Restore(anyhow!("Could not restore NET state {:?}", e))
});
}
Err(MigratableError::Restore(anyhow!(
"Could not find NET snapshot section"
)))
}
}
impl Transportable for Net {}
impl Migratable for Net {} impl Migratable for Net {}

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