Compare commits

..

350 Commits

Author SHA1 Message Date
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
93 changed files with 11399 additions and 3013 deletions

View File

@@ -1,4 +1,4 @@
name: Cloud Hypervisor Actions
name: Cloud Hypervisor Build
on: [pull_request, create]
jobs:
@@ -12,6 +12,9 @@ jobs:
- stable
- beta
- nightly
target:
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
steps:
- name: Code checkout
uses: actions/checkout@v2
@@ -19,52 +22,8 @@ jobs:
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
- name: Build
run: cargo build --release
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
run: cargo build --release --target=${{ matrix.target }}

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

@@ -0,0 +1,65 @@
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: 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
**/*.rs.bk
**/Cargo.lock

1237
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "cloud-hypervisor"
version = "0.6.0"
version = "0.7.0"
authors = ["The Cloud Hypervisor Authors"]
edition = "2018"
default-run = "cloud-hypervisor"
@@ -12,8 +12,9 @@ clap = { version = "2.33.0", features=["wrap_help"] }
epoll = ">=4.0.1"
futures = { version = "0.3.4", features = ["thread-pool"] }
lazy_static = "1.4.0"
libc = "0.2.68"
libc = "0.2.69"
log = { version = "0.4.8", features = ["std"] }
seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v0.21.1" }
serde_json = ">=1.0.9"
vhost_user_backend = { path = "vhost_user_backend"}
vhost_user_block = { path = "vhost_user_block"}
@@ -22,8 +23,8 @@ vhost_user_net = { path = "vhost_user_net"}
virtio-bindings = "0.1.0"
vmm = { path = "vmm" }
vm-device = { path = "vm-device" }
vm-memory = "0.1.0"
vmm-sys-util = "0.4.0"
vm-memory = "0.2.0"
vmm-sys-util = "0.5.0"
vm-virtio = { path = "vm-virtio" }
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
@@ -33,6 +34,8 @@ dirs = "2.0.2"
credibility = "0.1.3"
tempdir= "0.3.7"
lazy_static= "1.4.0"
tempfile = "3.1.0"
serde_json = ">=1.0.9"
[features]
default = ["acpi", "pci", "cmos"]
@@ -40,6 +43,7 @@ acpi = ["vmm/acpi"]
pci = ["vmm/pci_support"]
mmio = ["vmm/mmio_support"]
cmos = ["vmm/cmos"]
fwdebug = ["vmm/fwdebug"]
# Integration tests require a special environment to run in
integration_tests = []
@@ -53,6 +57,7 @@ members = [
"vmm",
"vm-virtio",
"vm-device",
"vm-migration",
"vhost_user_block",
"vhost_user_backend",
"vhost_user_fs",

12
Jenkinsfile vendored
View File

@@ -65,6 +65,18 @@ pipeline{
sh "scripts/dev_cli.sh tests --integration"
}
}
stage ('Run unit tests for musl') {
when { branch 'master' }
steps {
sh "scripts/dev_cli.sh tests --unit --libc musl"
}
}
stage ('Run integration tests for musl') {
when { branch 'master' }
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!
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)
* [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.

View File

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

View File

@@ -8,10 +8,10 @@ default = []
[dependencies]
byteorder = "1.3.4"
kvm-bindings = "0.2.0"
kvm-ioctls = "0.5.0"
libc = "0.2.68"
vm-memory = { version = "0.1.0", features = ["backend-mmap"] }
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch" }
kvm-ioctls = { git = "https://github.com/cloud-hypervisor/kvm-ioctls", branch = "ch" }
libc = "0.2.69"
vm-memory = { version = "0.2.0", features = ["backend-mmap"] }
acpi_tables = { path = "../acpi_tables", optional = true }
arch_gen = { path = "../arch_gen" }

View File

@@ -41,6 +41,10 @@ pub enum Error {
StartInfoPastRamEnd,
/// Error writing hvm_start_info to guest memory.
StartInfoSetup,
/// Failed to compute initramfs address.
InitramfsAddress,
/// Error writing module entry to guest memory.
ModlistSetup(vm_memory::GuestMemoryError),
}
pub type Result<T> = result::Result<T, Error>;
@@ -76,6 +80,21 @@ pub mod x86_64;
#[cfg(target_arch = "x86_64")]
pub use x86_64::{
arch_memory_regions, configure_system, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START,
BootProtocol, EntryPoint,
arch_memory_regions, configure_system, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
layout::CMDLINE_START, regs, BootProtocol, EntryPoint,
};
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
#[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,
}

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
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
/// with the zero page address since they are mutually exclusive.
pub const MEMMAP_START: GuestAddress = GuestAddress(0x7000);
@@ -74,18 +78,18 @@ pub const HIGH_RAM_START: GuestAddress = GuestAddress(0x100000);
// ** 32-bit reserved area (start: 3GiB, length: 1GiB) **
pub const MEM_32BIT_RESERVED_START: GuestAddress = GuestAddress(0xc000_0000);
pub const MEM_32BIT_RESERVED_SIZE: GuestUsize = (1024 << 20);
pub const MEM_32BIT_RESERVED_SIZE: GuestUsize = 1024 << 20;
// == Fixed constants within the "32-bit reserved" range ==
// Sub range: 32-bit PCI devices (start: 3GiB, length: 640Mib)
pub const MEM_32BIT_DEVICES_START: GuestAddress = MEM_32BIT_RESERVED_START;
pub const MEM_32BIT_DEVICES_SIZE: GuestUsize = (640 << 20);
pub const MEM_32BIT_DEVICES_SIZE: GuestUsize = 640 << 20;
// PCI MMCONFIG space (start: after the device space, length: 256MiB)
pub const PCI_MMCONFIG_START: GuestAddress =
GuestAddress(MEM_32BIT_DEVICES_START.0 + MEM_32BIT_DEVICES_SIZE);
pub const PCI_MMCONFIG_SIZE: GuestUsize = (256 << 20);
pub const PCI_MMCONFIG_SIZE: GuestUsize = 256 << 20;
// IOAPIC
pub const IOAPIC_START: GuestAddress = GuestAddress(0xfec0_0000);

View File

@@ -13,12 +13,16 @@ pub mod layout;
mod mptable;
pub mod regs;
use crate::InitramfsConfig;
use crate::RegionType;
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 vm_memory::{
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap, GuestUsize,
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap, GuestMemoryRegion,
GuestUsize,
};
#[derive(Debug, Copy, Clone)]
@@ -66,6 +70,11 @@ struct MemmapTableEntryWrapper(hvm_memmap_table_entry);
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
// trait (in this case `DataInit`) where:
// * the type that is implementing the trait is foreign or
@@ -151,6 +160,7 @@ pub fn configure_system(
guest_mem: &GuestMemoryMmap,
cmdline_addr: GuestAddress,
cmdline_size: usize,
initramfs: &Option<InitramfsConfig>,
num_cpus: u8,
setup_hdr: Option<setup_header>,
rsdp_addr: Option<GuestAddress>,
@@ -161,10 +171,17 @@ pub fn configure_system(
match boot_prot {
BootProtocol::PvhBoot => {
configure_pvh(guest_mem, cmdline_addr, rsdp_addr)?;
configure_pvh(guest_mem, cmdline_addr, initramfs, rsdp_addr)?;
}
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 +191,7 @@ pub fn configure_system(
fn configure_pvh(
guest_mem: &GuestMemoryMmap,
cmdline_addr: GuestAddress,
initramfs: &Option<InitramfsConfig>,
rsdp_addr: Option<GuestAddress>,
) -> super::Result<()> {
const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336ec578;
@@ -190,6 +208,24 @@ fn configure_pvh(
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
// at MEMMAP_START after all of the mappings are recorded.
let mut memmap: Vec<hvm_memmap_table_entry> = Vec::new();
@@ -293,6 +329,7 @@ fn configure_64bit_boot(
guest_mem: &GuestMemoryMmap,
cmdline_addr: GuestAddress,
cmdline_size: usize,
initramfs: &Option<InitramfsConfig>,
setup_hdr: Option<setup_header>,
rsdp_addr: Option<GuestAddress>,
) -> super::Result<()> {
@@ -319,6 +356,11 @@ fn configure_64bit_boot(
params.0.hdr.cmd_line_ptr = cmdline_addr.raw_value() 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)?;
let mem_end = guest_mem.last_addr();
@@ -388,6 +430,25 @@ fn add_e820_entry(
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)
}
#[cfg(test)]
mod tests {
use super::*;
@@ -417,6 +478,7 @@ mod tests {
&gm,
GuestAddress(0),
0,
&None,
1,
None,
None,
@@ -437,6 +499,7 @@ mod tests {
&gm,
GuestAddress(0),
0,
&None,
no_vcpus,
None,
None,
@@ -448,6 +511,7 @@ mod tests {
&gm,
GuestAddress(0),
0,
&None,
no_vcpus,
None,
None,
@@ -468,6 +532,7 @@ mod tests {
&gm,
GuestAddress(0),
0,
&None,
no_vcpus,
None,
None,
@@ -479,6 +544,7 @@ mod tests {
&gm,
GuestAddress(0),
0,
&None,
no_vcpus,
None,
None,
@@ -499,6 +565,7 @@ mod tests {
&gm,
GuestAddress(0),
0,
&None,
no_vcpus,
None,
None,
@@ -510,6 +577,7 @@ mod tests {
&gm,
GuestAddress(0),
0,
&None,
no_vcpus,
None,
None,

View File

@@ -70,7 +70,7 @@ pub fn setup_fpu(vcpu: &VcpuFd) -> Result<()> {
///
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
pub fn setup_msrs(vcpu: &VcpuFd) -> Result<()> {
vcpu.set_msrs(&create_msr_entries())
vcpu.set_msrs(&boot_msr_entries())
.map_err(Error::SetModelSpecificRegisters)?;
Ok(())
@@ -242,68 +242,43 @@ fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()>
Ok(())
}
fn create_msr_entries() -> Msrs {
let mut entries = Vec::<kvm_msr_entry>::new();
macro_rules! kvm_msr {
($msr:expr) => {
kvm_msr_entry {
index: $msr,
data: 0x0,
..Default::default()
}
};
}
entries.push(kvm_msr_entry {
index: msr_index::MSR_IA32_SYSENTER_CS,
data: 0x0,
..Default::default()
});
entries.push(kvm_msr_entry {
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()
});
macro_rules! kvm_msr_data {
($msr:expr, $data:expr) => {
kvm_msr_entry {
index: $msr,
data: $data,
..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)]
@@ -451,7 +426,7 @@ mod tests {
// 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
// expect.
let entry_vec = create_msr_entries();
let entry_vec = boot_msr_entries();
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"]
[dependencies]
anyhow = "1.0"
bitflags = ">=1.2.1"
byteorder = "1.3.4"
epoll = ">=4.0.1"
libc = "0.2.68"
libc = "0.2.69"
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" }
acpi_tables = { path = "../acpi_tables", optional = true }
vm-memory = "0.1.0"
vm-memory = "0.2.0"
vm-migration = { path = "../vm-migration" }
vmm-sys-util = ">=0.3.1"
[dev-dependencies]
@@ -21,3 +26,4 @@ tempfile = "3.1.0"
default = []
acpi = ["acpi_tables"]
cmos = []
fwdebug = []

View File

@@ -10,6 +10,7 @@
// See https://pdos.csail.mit.edu/6.828/2016/readings/ia32/ioapic.pdf for a specification.
use crate::BusDevice;
use anyhow::anyhow;
use byteorder::{ByteOrder, LittleEndian};
use std::io;
use std::result;
@@ -19,6 +20,14 @@ use vm_device::interrupt::{
MsiIrqGroupConfig, MsiIrqSourceConfig,
};
use vm_memory::GuestAddress;
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
Transportable,
};
#[derive(Serialize, Deserialize)]
#[serde(remote = "GuestAddress")]
pub struct GuestAddressDef(pub u64);
#[derive(Debug)]
pub enum Error {
@@ -99,25 +108,6 @@ fn set_remote_irr(entry: &mut RedirectionTableEntry, val: u8) {
*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;
const IOAPIC_VERSION_ID: u32 = 0x0017_0011;
@@ -166,13 +156,25 @@ fn decode_irq_from_selector(selector: u8) -> (usize, bool) {
}
pub struct Ioapic {
id: u32,
id: String,
id_reg: u32,
reg_sel: u32,
reg_entries: [RedirectionTableEntry; NUM_IOAPIC_PINS],
used_entries: [bool; NUM_IOAPIC_PINS],
apic_address: GuestAddress,
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 {
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
assert!(data.len() == 4);
@@ -210,6 +212,7 @@ impl BusDevice for Ioapic {
impl Ioapic {
pub fn new(
id: String,
apic_address: GuestAddress,
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
) -> Result<Ioapic> {
@@ -225,9 +228,11 @@ impl Ioapic {
.map_err(Error::EnableInterrupt)?;
Ok(Ioapic {
id: 0,
id,
id_reg: 0,
reg_sel: 0,
reg_entries: [0; NUM_IOAPIC_PINS],
used_entries: [false; NUM_IOAPIC_PINS],
apic_address,
interrupt_source_group,
})
@@ -342,7 +347,7 @@ impl Ioapic {
debug!("IOAPIC_W reg 0x{:x}, val 0x{:x}", self.reg_sel, val);
match self.reg_sel as u8 {
IOAPIC_REG_ID => self.id = (val >> 24) & 0xf,
IOAPIC_REG_ID => self.id_reg = (val >> 24) & 0xf,
IOWIN_OFF..=REG_MAX_OFFSET => {
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
if is_high_bits {
@@ -360,6 +365,8 @@ impl Ioapic {
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"),
}
@@ -370,7 +377,7 @@ impl Ioapic {
match self.reg_sel as u8 {
IOAPIC_REG_VERSION => IOAPIC_VERSION_ID,
IOAPIC_REG_ID | IOAPIC_REG_ARBITRATION_ID => (self.id & 0xf) << 24,
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 {
@@ -385,4 +392,74 @@ impl Ioapic {
}
}
}
fn state(&self) -> IoapicState {
IoapicState {
id_reg: self.id_reg,
reg_sel: self.reg_sel,
reg_entries: self.reg_entries,
used_entries: self.used_entries,
apic_address: self.apic_address,
}
}
fn set_state(&mut self, state: &IoapicState) -> Result<()> {
self.id_reg = state.id_reg;
self.reg_sel = state.reg_sel;
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)?;
}
}
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,14 @@
#[cfg(feature = "cmos")]
mod cmos;
#[cfg(feature = "fwdebug")]
mod fwdebug;
mod i8042;
mod serial;
#[cfg(feature = "cmos")]
pub use self::cmos::Cmos;
#[cfg(feature = "fwdebug")]
pub use self::fwdebug::FwDebugDevice;
pub use self::i8042::I8042Device;
pub use self::serial::Serial;

View File

@@ -6,10 +6,15 @@
// found in the LICENSE-BSD-3-Clause file.
use crate::BusDevice;
use anyhow::anyhow;
use std::collections::VecDeque;
use std::sync::Arc;
use std::{io, result};
use vm_device::interrupt::InterruptSourceGroup;
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
Transportable,
};
use vmm_sys_util::errno::Result;
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
/// guest, use `queue_input_bytes`.
pub struct Serial {
id: String,
interrupt_enable: u8,
interrupt_identification: u8,
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
@@ -68,12 +74,27 @@ pub struct Serial {
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 {
pub fn new(
id: String,
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
out: Option<Box<dyn io::Write + Send>>,
) -> Serial {
Serial {
id,
interrupt_enable: 0,
interrupt_identification: DEFAULT_INTERRUPT_IDENTIFICATION,
interrupt,
@@ -90,15 +111,16 @@ impl Serial {
/// Constructs a Serial port ready for output.
pub fn new_out(
id: String,
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
out: Box<dyn io::Write + Send>,
) -> Serial {
Self::new(interrupt, Some(out))
Self::new(id, interrupt, Some(out))
}
/// Constructs a Serial port with no connected output.
pub fn new_sink(interrupt: Arc<Box<dyn InterruptSourceGroup>>) -> Serial {
Self::new(interrupt, None)
pub fn new_sink(id: String, interrupt: Arc<Box<dyn InterruptSourceGroup>>) -> Serial {
Self::new(id, interrupt, None)
}
/// Queues raw bytes for the guest to read and signals the interrupt if the line status would
@@ -194,6 +216,32 @@ impl Serial {
}
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 {
@@ -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)]
mod tests {
use super::*;
@@ -244,6 +337,8 @@ mod tests {
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
use vmm_sys_util::eventfd::EventFd;
const SERIAL_NAME: &str = "serial";
struct TestInterrupt {
event_fd: EventFd,
}
@@ -294,6 +389,7 @@ mod tests {
let intr_evt = EventFd::new(0).unwrap();
let serial_out = SharedBuffer::new();
let mut serial = Serial::new_out(
String::from(SERIAL_NAME),
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
Box::new(serial_out.clone()),
);
@@ -313,6 +409,7 @@ mod tests {
let intr_evt = EventFd::new(0).unwrap();
let serial_out = SharedBuffer::new();
let mut serial = Serial::new_out(
String::from(SERIAL_NAME),
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
Box::new(serial_out.clone()),
);
@@ -350,9 +447,10 @@ mod tests {
#[test]
fn serial_thr() {
let intr_evt = EventFd::new(0).unwrap();
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
intr_evt.try_clone().unwrap(),
))));
let mut serial = Serial::new_sink(
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
// counter doesn't change (for 0 it blocks)
@@ -371,9 +469,10 @@ mod tests {
#[test]
fn serial_dlab() {
let intr_evt = EventFd::new(0).unwrap();
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
intr_evt.try_clone().unwrap(),
))));
let mut serial = Serial::new_sink(
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, DLAB_LOW as u64, &[0x12 as u8]);
@@ -391,9 +490,10 @@ mod tests {
#[test]
fn serial_modem() {
let intr_evt = EventFd::new(0).unwrap();
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
intr_evt.try_clone().unwrap(),
))));
let mut serial = Serial::new_sink(
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, DATA as u64, &['a' as u8]);
@@ -416,9 +516,10 @@ mod tests {
#[test]
fn serial_scratch() {
let intr_evt = EventFd::new(0).unwrap();
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
intr_evt.try_clone().unwrap(),
))));
let mut serial = Serial::new_sink(
String::from(SERIAL_NAME),
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
);
serial.write(0, SCR as u64, &[0x12 as u8]);

View File

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

View File

@@ -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
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
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

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,wce=true" \
--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,9 +4,9 @@ version = "0.1.0"
authors = ["The Chromium OS Authors"]
[dependencies]
libc = "0.2.68"
libc = "0.2.69"
rand = "0.7.3"
serde = "1.0.105"
serde = "1.0.106"
vmm-sys-util = ">=0.3.1"
net_gen = { path = "../net_gen" }
@@ -14,4 +14,4 @@ net_gen = { path = "../net_gen" }
[dev-dependencies]
lazy_static = "1.3.0"
pnet = "0.25.0"
serde_json = "1.0.48"
serde_json = "1.0.52"

View File

@@ -9,6 +9,7 @@ use rand::Rng;
use std::fmt;
use std::io;
use std::result::Result;
use std::str::FromStr;
use serde::de::{Deserialize, Deserializer, Error};
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)]
mod tests {
extern crate serde_json;

View File

@@ -8,7 +8,7 @@ edition = "2018"
vm-allocator = { path = "../vm-allocator" }
byteorder = "1.3.4"
devices = { path = "../devices" }
libc = "0.2.68"
libc = "0.2.69"
log = "0.4.8"
vm-device = { path = "../vm-device" }
vm-memory = "0.1.0"
vm-memory = "0.2.0"

View File

@@ -53,6 +53,7 @@ impl PciRoot {
config: PciConfiguration::new(
VENDOR_ID_INTEL,
DEVICE_ID_INTEL_VIRT_PCIE_HOST,
0,
PciClassCode::BridgeDevice,
&PciBridgeSubclass::HostBridge,
None,
@@ -239,7 +240,10 @@ impl PciConfigIo {
device.deref_mut(),
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 +359,10 @@ impl PciConfigMmio {
device.deref_mut(),
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
);
}
}

View File

@@ -244,6 +244,38 @@ pub trait PciCapability {
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
}
/// Contains the configuration space of a PCI node.
/// See the [specification](https://en.wikipedia.org/wiki/PCI_configuration_space).
/// The configuration space is accessed with DWORD reads and writes from the guest.
@@ -297,6 +329,10 @@ pub enum Error {
CapabilityEmpty,
CapabilityLengthInvalid(usize),
CapabilitySpaceFull(usize),
Decode32BarSize,
Decode64BarSize,
Encode32BarSize,
Encode64BarSize,
RomBarAddressInvalid(u64, u64),
RomBarInUse(usize),
RomBarInvalid(usize),
@@ -324,6 +360,10 @@ impl Display for Error {
CapabilityEmpty => write!(f, "empty capabilities are invalid"),
CapabilityLengthInvalid(l) => write!(f, "Invalid capability length {}", l),
CapabilitySpaceFull(s) => write!(f, "capability of size {} doesn't fit", s),
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),
RomBarInUse(b) => write!(f, "rom bar {} already used", b),
RomBarInvalid(b) => write!(f, "rom bar {} invalid, max {}", b, NUM_BAR_REGS - 1),
@@ -337,6 +377,7 @@ impl PciConfiguration {
pub fn new(
vendor_id: u16,
device_id: u16,
revision_id: u8,
class_code: PciClassCode,
subclass: &dyn PciSubclass,
programming_interface: Option<&dyn PciProgrammingInterface>,
@@ -359,7 +400,8 @@ impl PciConfiguration {
};
registers[2] = u32::from(class_code.get_register_value()) << 24
| 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)
match header_type {
PciHeaderType::Device => {
@@ -403,14 +445,14 @@ impl PciConfiguration {
// Handle very specific case where the BAR is being written with
// all 1's to retrieve the BAR size during next BAR reading.
if value == 0xffff_ffff {
mask = self.bar_size[reg_idx - 4];
mask &= self.bar_size[reg_idx - 4];
}
} else if reg_idx == ROM_BAR_REG {
// Handle very specific case where the BAR is being written with
// all 1's on bits 31-11 to retrieve the BAR size during next BAR
// reading.
if value & ROM_BAR_ADDR_MASK == ROM_BAR_ADDR_MASK {
mask = self.rom_bar_size;
mask &= self.rom_bar_size;
}
}
@@ -494,6 +536,11 @@ impl PciConfiguration {
if end_addr > u64::from(u32::max_value()) {
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 => {
if config.reg_idx + 1 >= NUM_BAR_REGS {
@@ -508,10 +555,16 @@ impl PciConfiguration {
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.writable_bits[bar_idx + 1] = 0xffff_ffff;
self.bar_addr[config.reg_idx + 1] = self.registers[bar_idx + 1];
self.bar_size[config.reg_idx + 1] = (config.size >> 32) as u32;
self.bar_size[config.reg_idx] = bar_size_lo;
self.bar_size[config.reg_idx + 1] = bar_size_hi;
self.bar_used[config.reg_idx + 1] = true;
}
}
@@ -527,7 +580,6 @@ impl PciConfiguration {
self.registers[bar_idx] = ((config.addr as u32) & mask) | lower_bits;
self.writable_bits[bar_idx] = mask;
self.bar_addr[config.reg_idx] = self.registers[bar_idx];
self.bar_size[config.reg_idx] = config.size as u32;
self.bar_used[config.reg_idx] = true;
self.bar_type[config.reg_idx] = Some(config.region_type);
Ok(config.reg_idx)
@@ -559,7 +611,8 @@ impl PciConfiguration {
self.registers[config.reg_idx] = (config.addr as u32) | active;
self.writable_bits[config.reg_idx] = ROM_BAR_ADDR_MASK;
self.rom_bar_addr = self.registers[config.reg_idx];
self.rom_bar_size = config.size as u32;
self.rom_bar_size =
encode_32_bits_bar_size(config.size as u32).ok_or(Error::Encode32BarSize)?;
self.rom_bar_used = true;
Ok(config.reg_idx)
}
@@ -697,7 +750,11 @@ impl PciConfiguration {
);
let old_base = u64::from(self.bar_addr[bar_idx] & mask);
let new_base = u64::from(value & mask);
let len = u64::from(self.bar_size[bar_idx]);
let len = u64::from(
decode_32_bits_bar_size(self.bar_size[bar_idx])
.ok_or(Error::Decode32BarSize)
.unwrap(),
);
let region_type = bar_type;
self.bar_addr[bar_idx] = value;
@@ -733,8 +790,10 @@ impl PciConfiguration {
| u64::from(self.bar_addr[bar_idx - 1] & self.writable_bits[reg_idx - 1]);
let new_base = u64::from(value & mask) << 32
| u64::from(self.registers[reg_idx - 1] & self.writable_bits[reg_idx - 1]);
let len = u64::from(self.bar_size[bar_idx]) << 32
| u64::from(self.bar_size[bar_idx - 1]);
let len =
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;
self.bar_addr[bar_idx] = value;
@@ -760,7 +819,11 @@ impl PciConfiguration {
);
let old_base = u64::from(self.rom_bar_addr & mask);
let new_base = u64::from(value & mask);
let len = u64::from(self.rom_bar_size);
let len = u64::from(
decode_32_bits_bar_size(self.rom_bar_size)
.ok_or(Error::Decode32BarSize)
.unwrap(),
);
let region_type = PciBarRegionType::Memory32BitRegion;
self.rom_bar_addr = value;
@@ -862,6 +925,7 @@ mod tests {
let mut cfg = PciConfiguration::new(
0x1234,
0x5678,
0x1,
PciClassCode::MultimediaController,
&PciMultimediaSubclass::AudioController,
None,
@@ -917,6 +981,7 @@ mod tests {
let cfg = PciConfiguration::new(
0x1234,
0x5678,
0x1,
PciClassCode::MultimediaController,
&PciMultimediaSubclass::AudioController,
Some(&TestPI::Test),

View File

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

View File

@@ -1,10 +1,20 @@
- [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)
- [Contributors](#contributors)
- [v0.6.0](#v060)
- [Directly Assigned Devices Hotplug](#directly-assigned-devices-hotplug)
- [Shared Filesystem Improvements](#shared-filesystem-improvements)
- [Block and Networking IO Self Offloading](#block-and-networking-io-self-offloading)
- [Command Line Interface](#command-line-interface)
- [PVH Boot](#pvh-boot)
- [Contributors](#contributors)
- [Contributors](#contributors-1)
- [v0.5.1](#v051)
- [v0.5.0](#v050)
- [Virtual Machine Dynamic Resizing](#virtual-machine-dynamic-resizing)
@@ -12,7 +22,7 @@
- [New Interrupt Management Framework](#new-interrupt-management-framework)
- [Development Tools](#development-tools)
- [Kata Containers Integration](#kata-containers-integration)
- [Contributors](#contributors)
- [Contributors](#contributors-2)
- [v0.4.0](#v040)
- [Dynamic virtual CPUs addition](#dynamic-virtual-cpus-addition)
- [Programmatic firmware tables generation](#programmatic-firmware-tables-generation)
@@ -21,7 +31,7 @@
- [Userspace IOAPIC by default](#userspace-ioapic-by-default)
- [PCI BAR reprogramming](#pci-bar-reprogramming)
- [New `cloud-hypervisor` organization](#new-cloud-hypervisor-organization)
- [Contributors](#contributors-1)
- [Contributors](#contributors-3)
- [v0.3.0](#v030)
- [Block device offloading](#block-device-offloading)
- [Network device backend](#network-device-backend)
@@ -48,6 +58,105 @@
- [Unit testing](#unit-testing)
- [Integration tests parallelization](#integration-tests-parallelization)
# 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
This release has been tracked through the [0.6.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/7).

View File

@@ -21,6 +21,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq \
wget \
sudo \
mtools \
musl-tools \
libssl-dev \
pkg-config \
flex \
@@ -34,11 +35,22 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq \
libcap-ng-dev \
socat \
dosfstools \
cpio \
bsdtar \
gcc-multilib \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Fix the libssl-dev install
RUN cp /usr/include/x86_64-linux-gnu/openssl/opensslconf.h /usr/include/openssl/
ENV OPENSSL_DIR=/usr/lib/x86_64-linux-gnu/
ENV OPENSSL_LIB_DIR=/usr/lib/x86_64-linux-gnu/
ENV OPENSSL_INCLUDE_DIR=/usr/include/
# Install the rust toolchain
RUN nohup curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain "$RUST_TOOLCHAIN" \
&& rustup target add x86_64-unknown-linux-musl --toolchain "$RUST_TOOLCHAIN" \
&& rustup toolchain add $RUST_TOOLCHAIN-x86_64-unknown-linux-musl \
&& rustup component add rustfmt \
&& rustup component add clippy \
&& cargo install cargo-audit \

View File

@@ -178,7 +178,14 @@ CONFIG_NET_NS=y
CONFIG_SCHED_AUTOGROUP=y
# CONFIG_SYSFS_DEPRECATED is not set
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_CC_OPTIMIZE_FOR_PERFORMANCE=y
# CONFIG_CC_OPTIMIZE_FOR_SIZE is not set
@@ -485,6 +492,7 @@ CONFIG_ACPI_HOTPLUG_CPU=y
# CONFIG_ACPI_PROCESSOR_AGGREGATOR is not set
CONFIG_ACPI_THERMAL=y
CONFIG_ARCH_HAS_ACPI_TABLE_UPGRADE=y
CONFIG_ACPI_TABLE_UPGRADE=y
# CONFIG_ACPI_DEBUG is not set
# CONFIG_ACPI_PCI_SLOT is not set
CONFIG_ACPI_CONTAINER=y
@@ -2418,7 +2426,22 @@ CONFIG_ZLIB_INFLATE=y
CONFIG_ZLIB_DEFLATE=y
CONFIG_LZO_COMPRESS=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_XARRAY_MULTI=y
CONFIG_ASSOCIATIVE_ARRAY=y

View File

@@ -7,7 +7,7 @@
CLI_NAME="Cloud Hypervisor"
CTR_IMAGE_TAG="cloudhypervisor/dev"
CTR_IMAGE_VERSION="v1"
CTR_IMAGE_VERSION="v2"
CTR_IMAGE="${CTR_IMAGE_TAG}:${CTR_IMAGE_VERSION}"
DOCKER_RUNTIME="docker"
@@ -113,6 +113,13 @@ ensure_build_dir() {
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.
# 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
@@ -139,16 +146,18 @@ cmd_help() {
echo ""
echo "Available commands:"
echo ""
echo " build [--debug|--release] [-- [<cargo args>]]"
echo " build [--debug|--release] [--libc musl|gnu] [-- [<cargo args>]]"
echo " Build the Cloud Hypervisor binaries."
echo " --debug Build the debug binaries. This is the default."
echo " --release Build the release binaries."
echo " --libc Select the C library Cloud Hypervisor will be built against. Default is gnu"
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 " --unit Run the unit tests."
echo " --cargo Run the cargo 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 ""
echo " build-container [--type]"
@@ -165,12 +174,19 @@ cmd_help() {
cmd_build() {
build="debug"
libc="gnu"
while [ $# -gt 0 ]; do
case "$1" in
"-h"|"--help") { cmd_help; exit 1; } ;;
"--debug") { build="debug"; } ;;
"--release") { build="release"; } ;;
"--libc")
shift
[[ "$1" =~ ^(musl|gnu)$ ]] || \
die "Invalid libc: $1. Valid options are \"musl\" and \"gnu\"."
libc="$1"
;;
"--") { shift; break; } ;;
*)
die "Unknown build argument: $1. Please use --help for help."
@@ -179,8 +195,11 @@ cmd_build() {
shift
done
target="$(uname -m)-unknown-linux-${libc}"
cargo_args=("$@")
[ $build = "release" ] && cargo_args+=("--release")
cargo_args+=(--target "$target")
$DOCKER_RUNTIME run \
--user "$(id -u):$(id -g)" \
@@ -191,7 +210,7 @@ cmd_build() {
"$CTR_IMAGE" \
cargo build \
--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() {
@@ -212,15 +231,22 @@ cmd_tests() {
unit=false
cargo=false
integration=false
libc="gnu"
while [ $# -gt 0 ]; do
case "$1" in
"-h"|"--help") { cmd_help; exit 1; } ;;
"--unit") { unit=true; } ;;
"--cargo") { cargo=true; } ;;
"--integration") { integration=true; } ;;
"--all") { cargo=true; unit=true; integration=true; } ;;
"--") { shift; break; } ;;
"-h"|"--help") { cmd_help; exit 1; } ;;
"--unit") { unit=true; } ;;
"--cargo") { cargo=true; } ;;
"--integration") { integration=true; } ;;
"--libc")
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."
;;
@@ -228,8 +254,16 @@ cmd_tests() {
shift
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
say "Running unit tests..."
say "Running unit tests for $target..."
$DOCKER_RUNTIME run \
--workdir "$CTR_CLH_ROOT_DIR" \
--rm \
@@ -237,6 +271,9 @@ cmd_tests() {
--device /dev/net/tun \
--cap-add net_admin \
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
--env BUILD_TARGET="$target" \
--env CFLAGS="$cflags" \
--env TARGET_CC="$target_cc" \
"$CTR_IMAGE" \
./scripts/run_unit_tests.sh "$@" || fix_dir_perms $? || exit $?
fi
@@ -252,7 +289,7 @@ cmd_tests() {
fi
if [ "$integration" = true ] ; then
say "Running integration tests..."
say "Running integration tests for $target..."
$DOCKER_RUNTIME run \
--workdir "$CTR_CLH_ROOT_DIR" \
--rm \
@@ -265,6 +302,9 @@ cmd_tests() {
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
--env USER="root" \
--env BUILD_TARGET="$target" \
--env CFLAGS="$cflags" \
--env TARGET_CC="$target_cc" \
"$CTR_IMAGE" \
./scripts/run_integration_tests.sh "$@" || fix_dir_perms $? || exit $?
fi
@@ -325,5 +365,6 @@ cmd=cmd_$1
shift
ensure_build_dir
ensure_latest_ctr
$cmd "$@"

View File

@@ -3,6 +3,8 @@ set -x
source $HOME/.cargo/env
export BUILD_TARGET=${BUILD_TARGET-x86_64-unknown-linux-gnu}
WORKLOADS_DIR="$HOME/workloads"
mkdir -p "$WORKLOADS_DIR"
@@ -51,20 +53,46 @@ if [ ! -f "$BIONIC_OS_RAW_IMAGE" ]; then
fi
EOAN_OS_IMAGE_NAME="eoan-server-cloudimg-amd64.img"
EOAN_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$EOAN_OS_IMAGE_NAME"
EOAN_OS_IMAGE="$WORKLOADS_DIR/$EOAN_OS_IMAGE_NAME"
if [ ! -f "$EOAN_OS_IMAGE" ]; then
FOCAL_OS_IMAGE_NAME="focal-server-cloudimg-amd64.img"
FOCAL_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$FOCAL_OS_IMAGE_NAME"
FOCAL_OS_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_IMAGE_NAME"
if [ ! -f "$FOCAL_OS_IMAGE" ]; then
pushd $WORKLOADS_DIR
time wget --quiet $EOAN_OS_IMAGE_URL || exit 1
time wget --quiet $FOCAL_OS_IMAGE_URL || exit 1
popd
fi
EOAN_OS_RAW_IMAGE_NAME="eoan-server-cloudimg-amd64-raw.img"
EOAN_OS_RAW_IMAGE="$WORKLOADS_DIR/$EOAN_OS_RAW_IMAGE_NAME"
if [ ! -f "$EOAN_OS_RAW_IMAGE" ]; then
FOCAL_OS_RAW_IMAGE_NAME="focal-server-cloudimg-amd64-raw.img"
FOCAL_OS_RAW_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_RAW_IMAGE_NAME"
if [ ! -f "$FOCAL_OS_RAW_IMAGE" ]; then
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
fi
@@ -182,13 +210,13 @@ sudo ip tuntap add name vunet-tap0 mode tap
# Create tap interface with multipe queues support for vhost_user_net test.
sudo ip tuntap add name vunet-tap1 mode tap multi_queue
cargo build --release
sudo setcap cap_net_admin+ep target/release/cloud-hypervisor
sudo setcap cap_net_admin+ep target/release/vhost_user_net
cargo build --release --target $BUILD_TARGET
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.
cp target/release/cloud-hypervisor $VFIO_DIR
cp target/release/ch-remote $VFIO_DIR
cp target/$BUILD_TARGET/release/cloud-hypervisor $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
# for the memory to be merged between two processes.
@@ -213,8 +241,12 @@ RES=$?
if [ $RES -eq 0 ]; then
# virtio-mmio based testing
cargo build --release --no-default-features --features "mmio"
sudo setcap cap_net_admin+ep target/release/cloud-hypervisor
cargo build --release --target $BUILD_TARGET --no-default-features --features "mmio"
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
export RUST_BACKTRACE=1

View File

@@ -2,13 +2,15 @@
source $HOME/.cargo/env
cargo test --workspace --no-run
pushd target/debug
BUILD_TARGET=${BUILD_TARGET-x86_64-unknown-linux-gnu}
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
popd
sudo adduser $USER kvm
newgrp kvm << EOF || exit 1
export RUST_BACKTRACE=1
cargo test --workspace "$@" || exit 1;
cargo test --target $BUILD_TARGET --workspace "$@" || exit 1;
EOF

View File

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

View File

@@ -23,6 +23,12 @@ enum Error {
InvalidCPUCount(std::num::ParseIntError),
InvalidMemorySize(std::num::ParseIntError),
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),
}
#[derive(Clone, Copy, Debug)]
@@ -213,6 +219,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> {
let mut socket =
UnixStream::connect(matches.value_of("api-socket").unwrap()).map_err(Error::Socket)?;
@@ -246,6 +331,62 @@ fn do_command(matches: &ArgMatches) -> Result<(), Error> {
.value_of("id")
.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),
None => unreachable!(),
}
@@ -273,6 +414,51 @@ fn main() {
.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::with_name("remove-device")
.about("Remove VFIO device")
@@ -300,7 +486,25 @@ fn main() {
),
)
.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();

View File

@@ -25,7 +25,7 @@ fn main() {
.long("block-backend")
.help(
"vhost-user-block backend parameters \
\"image=<image_path>,sock=<socket_path>,num_queues=<number_of_queues>,\
\"path=<image_path>,socket=<socket_path>,num_queues=<number_of_queues>,\
readonly=true|false,direct=true|false,poll_queue=true|false\"",
)
.takes_value(true)

View File

@@ -15,7 +15,7 @@ use futures::executor::{ThreadPool, ThreadPoolBuilder};
use libc::EFD_NONBLOCK;
use log::*;
use std::num::Wrapping;
use std::sync::{Arc, RwLock};
use std::sync::{Arc, Mutex, RwLock};
use std::{convert, error, fmt, io, process};
use vhost_rs::vhost_user::message::*;
@@ -83,7 +83,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>>,
kill_evt: EventFd,
server: Arc<Server<F>>,
@@ -93,9 +93,9 @@ struct VhostUserFsBackend<F: FileSystem + Send + Sync + 'static> {
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 {
VhostUserFsBackend {
VhostUserFsThread {
mem: self.mem.clone(),
kill_evt: self.kill_evt.try_clone().unwrap(),
server: self.server.clone(),
@@ -106,9 +106,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> {
Ok(VhostUserFsBackend {
Ok(VhostUserFsThread {
mem: None,
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?,
server: Arc::new(Server::new(fs)),
@@ -176,6 +176,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> {
fn num_queues(&self) -> usize {
NUM_QUEUES
@@ -197,25 +208,27 @@ impl<F: FileSystem + Send + Sync + 'static> VhostUserBackend for VhostUserFsBack
}
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<()> {
self.mem = Some(GuestMemoryAtomic::new(mem));
self.thread.lock().unwrap().mem = Some(GuestMemoryAtomic::new(mem));
Ok(())
}
fn handle_event(
&mut self,
&self,
device_event: u16,
evset: epoll::Events,
vrings: &[Arc<RwLock<Vring>>],
_thread_id: usize,
) -> VhostUserBackendResult<bool> {
if evset != epoll::Events::EPOLLIN {
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(),
None => return Err(Error::NoMemoryConfigured.into()),
};
@@ -232,7 +245,7 @@ impl<F: FileSystem + Send + Sync + 'static> VhostUserBackend for VhostUserFsBack
_ => return Err(Error::HandleEventUnknownEvent.into()),
};
if self.event_idx {
if thread.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
@@ -242,24 +255,27 @@ impl<F: FileSystem + Send + Sync + 'static> VhostUserBackend for VhostUserFsBack
let mut vring = vring_lock.write().unwrap();
vring.mut_queue().update_avail_event(&mem);
}
if !self.process_queue(vring_lock.clone())? {
if !thread.process_queue(vring_lock.clone())? {
break;
}
}
} else {
// Without EVENT_IDX, a single call is enough.
self.process_queue(vring_lock)?;
thread.process_queue(vring_lock)?;
}
Ok(false)
}
fn exit_event(&self) -> Option<(EventFd, Option<u16>)> {
Some((self.kill_evt.try_clone().unwrap(), Some(KILL_EVENT)))
fn exit_event(&self, _thread_index: usize) -> Option<(EventFd, Option<u16>)> {
Some((
self.thread.lock().unwrap().kill_evt.try_clone().unwrap(),
Some(KILL_EVENT),
))
}
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);
}
}
@@ -338,7 +354,15 @@ fn main() {
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) {
error!("Error shutting down worker thread: {:?}", e)
}

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 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"
# 512M ram requires 256 pages
echo 256 | sudo tee /proc/sys/vm/nr_hugepages
# 1G ram requires 512 pages
echo 512 | sudo tee /proc/sys/vm/nr_hugepages
sudo chmod a+rwX /dev/hugepages
/mnt/cloud-hypervisor --kernel /mnt/vmlinux --cmdline "console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=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

File diff suppressed because it is too large Load Diff

View File

@@ -7,14 +7,14 @@ authors = ["The Cloud Hypervisor Authors"]
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"
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch" }
kvm-ioctls = { git = "https://github.com/cloud-hypervisor/kvm-ioctls", branch = "ch" }
libc = "0.2.69"
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"] }
vm-memory = { version = "0.2.0", features = ["backend-mmap"] }
vmm-sys-util = ">=0.3.1"
[dependencies.vfio-bindings]

View File

@@ -25,7 +25,7 @@ use vfio_ioctls::*;
use vm_device::{get_host_address_range, ExternalDmaMapping};
use vm_memory::{
Address, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, GuestMemoryMmap,
GuestMemoryRegion,
GuestMemoryRegion, GuestRegionMmap,
};
use vmm_sys_util::eventfd::EventFd;
use vmm_sys_util::fam::FamStruct;
@@ -873,6 +873,18 @@ impl VfioDevice {
Ok(())
}
pub fn extend_dma_map(&self, new_region: &Arc<GuestRegionMmap>) -> Result<()> {
if !self.iommu_attached {
self.vfio_dma_map(
new_region.start_addr().raw_value(),
new_region.len() as u64,
new_region.as_ptr() as u64,
)
} else {
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 {

View File

@@ -27,7 +27,7 @@ use vm_allocator::SystemAllocator;
use vm_device::interrupt::{
InterruptIndex, InterruptManager, InterruptSourceGroup, MsiIrqGroupConfig,
};
use vm_memory::{Address, GuestAddress, GuestUsize};
use vm_memory::{Address, GuestAddress, GuestRegionMmap, GuestUsize};
use vmm_sys_util::eventfd::EventFd;
#[derive(Debug)]
@@ -41,6 +41,7 @@ pub enum VfioPciError {
SetGsiRouting(kvm_ioctls::Error),
MsiNotConfigured,
MsixNotConfigured,
UpdateMemory(crate::VfioError),
UpdateMsiEventFd,
UpdateMsixEventFd,
}
@@ -62,6 +63,7 @@ impl fmt::Display for VfioPciError {
VfioPciError::SetGsiRouting(e) => write!(f, "failed to set GSI routes for KVM: {}", e),
VfioPciError::MsiNotConfigured => write!(f, "MSI interrupt not yet configured"),
VfioPciError::MsixNotConfigured => write!(f, "MSI-X interrupt not yet configured"),
VfioPciError::UpdateMemory(e) => write!(f, "failed to update memory: {}", e),
VfioPciError::UpdateMsiEventFd => write!(f, "failed to update MSI eventfd"),
VfioPciError::UpdateMsixEventFd => write!(f, "failed to update MSI-X eventfd"),
}
@@ -295,6 +297,7 @@ impl VfioPciDevice {
device.reset();
let configuration = PciConfiguration::new(
0,
0,
0,
PciClassCode::Other,
@@ -610,6 +613,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 {

View File

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

View File

@@ -81,10 +81,11 @@ pub trait VhostUserBackend: Send + Sync + 'static {
/// virtqueues on its own, but does not know what to do with events
/// happening on custom listeners.
fn handle_event(
&mut self,
&self,
device_event: u16,
evset: epoll::Events,
vrings: &[Arc<RwLock<Vring>>],
thread_id: usize,
) -> result::Result<bool, io::Error>;
/// Get virtio device configuration.
@@ -105,7 +106,7 @@ pub trait VhostUserBackend: Send + Sync + 'static {
/// 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
/// after the last queue
fn exit_event(&self) -> Option<(EventFd, Option<u16>)> {
fn exit_event(&self, _thread_index: usize) -> Option<(EventFd, Option<u16>)> {
None
}
@@ -113,6 +114,10 @@ pub trait VhostUserBackend: Send + Sync + 'static {
/// A default implementation is provided as we cannot expect all backends
/// to implement this function.
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
@@ -187,8 +192,8 @@ impl<S: VhostUserBackend> VhostUserDaemon<S> {
/// Retrieve the vring worker. This is necessary to perform further
/// actions like registering and unregistering some extra event file
/// descriptors.
pub fn get_vring_worker(&self) -> Arc<VringWorker> {
self.handler.lock().unwrap().get_vring_worker()
pub fn get_vring_workers(&self) -> Vec<Arc<VringWorker>> {
self.handler.lock().unwrap().get_vring_workers()
}
}
@@ -283,6 +288,7 @@ struct VringEpollHandler<S: VhostUserBackend> {
backend: Arc<RwLock<S>>,
vrings: Vec<Arc<RwLock<Vring>>>,
exit_event_id: Option<u16>,
thread_id: usize,
}
impl<S: VhostUserBackend> VringEpollHandler<S> {
@@ -310,9 +316,9 @@ impl<S: VhostUserBackend> VringEpollHandler<S> {
}
self.backend
.write()
.read()
.unwrap()
.handle_event(device_event, evset, &self.vrings)
.handle_event(device_event, evset, &self.vrings, self.thread_id)
.map_err(VringEpollHandlerError::HandleEventBackendHandling)
}
}
@@ -453,22 +459,24 @@ type VhostUserHandlerResult<T> = std::result::Result<T, VhostUserHandlerError>;
struct VhostUserHandler<S: VhostUserBackend> {
backend: Arc<RwLock<S>>,
worker: Arc<VringWorker>,
workers: Vec<Arc<VringWorker>>,
owned: bool,
features_acked: bool,
acked_features: u64,
acked_protocol_features: u64,
num_queues: usize,
max_queue_size: usize,
queues_per_thread: Vec<u64>,
memory: Option<Memory>,
vrings: Vec<Arc<RwLock<Vring>>>,
worker_thread: Option<thread::JoinHandle<VringWorkerResult<()>>>,
worker_threads: Vec<thread::JoinHandle<VringWorkerResult<()>>>,
}
impl<S: VhostUserBackend> VhostUserHandler<S> {
fn new(backend: Arc<RwLock<S>>) -> VhostUserHandlerResult<Self> {
let num_queues = backend.read().unwrap().num_queues();
let max_queue_size = backend.read().unwrap().max_queue_size();
let queues_per_thread = backend.read().unwrap().queues_per_thread();
let mut vrings: Vec<Arc<RwLock<Vring>>> = Vec::new();
for _ in 0..num_queues {
@@ -476,14 +484,18 @@ impl<S: VhostUserBackend> VhostUserHandler<S> {
vrings.push(vring);
}
// Create the epoll file descriptor
let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?;
let mut workers = Vec::new();
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)?;
let vring_worker = Arc::new(VringWorker { epoll_fd });
let worker = vring_worker.clone();
let vring_worker = Arc::new(VringWorker { epoll_fd });
let worker = vring_worker.clone();
let exit_event_id =
if let Some((exit_event_fd, exit_event_id)) = backend.read().unwrap().exit_event() {
let exit_event_id = if let Some((exit_event_fd, exit_event_id)) =
backend.read().unwrap().exit_event(thread_id)
{
let exit_event_id = exit_event_id.unwrap_or(num_queues as u16);
worker
.register_listener(
@@ -497,36 +509,47 @@ impl<S: VhostUserBackend> VhostUserHandler<S> {
None
};
let vring_handler = VringEpollHandler {
backend: backend.clone(),
vrings: vrings.clone(),
exit_event_id,
};
let mut thread_vrings: Vec<Arc<RwLock<Vring>>> = Vec::new();
for (index, vring) in vrings.iter().enumerate() {
if (queues_mask >> index) & 1u64 == 1u64 {
thread_vrings.push(vring.clone());
}
}
let worker_thread = Some(
thread::Builder::new()
let vring_handler = VringEpollHandler {
backend: backend.clone(),
vrings: thread_vrings,
exit_event_id,
thread_id,
};
let worker_thread = thread::Builder::new()
.name("vring_worker".to_string())
.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 {
backend,
worker,
workers,
owned: false,
features_acked: false,
acked_features: 0,
acked_protocol_features: 0,
num_queues,
max_queue_size,
queues_per_thread,
memory: None,
vrings,
worker_thread,
worker_threads,
})
}
fn get_vring_worker(&self) -> Arc<VringWorker> {
self.worker.clone()
fn get_vring_workers(&self) -> Vec<Arc<VringWorker>> {
self.workers.clone()
}
fn vmm_va_to_gpa(&self, vmm_va: u64) -> VhostUserHandlerResult<u64> {
@@ -718,9 +741,20 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
// VHOST_USER_GET_VRING_BASE.
self.vrings[index as usize].write().unwrap().queue.ready = false;
if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() {
self.worker
.unregister_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(index))
.map_err(VhostUserError::ReqHandlerError)?;
for (thread_index, queues_mask) in self.queues_per_thread.iter().enumerate() {
let shifted_queues_mask = queues_mask >> index;
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]
@@ -752,9 +786,20 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
// VHOST_USER_GET_VRING_BASE.
self.vrings[index as usize].write().unwrap().queue.ready = true;
if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() {
self.worker
.register_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(index))
.map_err(VhostUserError::ReqHandlerError)?;
for (thread_index, queues_mask) in self.queues_per_thread.iter().enumerate() {
let shifted_queues_mask = queues_mask >> index;
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(())
@@ -837,7 +882,7 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
impl<S: VhostUserBackend> Drop for VhostUserHandler<S> {
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() {
error!("Error in vring worker: {:?}", e);
}

View File

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

View File

@@ -23,19 +23,20 @@ use std::io::Read;
use std::io::{Seek, SeekFrom, Write};
use std::mem;
use std::num::Wrapping;
use std::ops::DerefMut;
use std::os::unix::fs::OpenOptionsExt;
use std::path::PathBuf;
use std::process;
use std::slice;
use std::sync::{Arc, RwLock};
use std::sync::{Arc, Mutex, RwLock};
use std::time::Instant;
use std::vec::Vec;
use std::{convert, error, fmt, io};
use vhost_rs::vhost_user::message::*;
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring, VringWorker};
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring};
use virtio_bindings::bindings::virtio_blk::*;
use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
use vm_memory::{Bytes, GuestMemoryError, GuestMemoryMmap};
use vm_memory::{Bytes, GuestMemoryMmap};
use vm_virtio::block::{build_disk_image_id, Request};
use vmm_sys_util::eventfd::EventFd;
@@ -51,17 +52,11 @@ const POLL_QUEUE_US: u128 = 50;
trait DiskFile: Read + Seek + Write + Send + Sync {}
impl<D: Read + Seek + Write + Send + Sync> DiskFile for D {}
pub type Result<T> = std::result::Result<T, Error>;
pub type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>;
type Result<T> = std::result::Result<T, Error>;
type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>;
#[derive(Debug)]
pub enum Error {
/// Failed to detect image type.
DetectImageType,
/// Bad memory address.
GuestMemory(GuestMemoryError),
/// Can't open image file.
OpenImage,
enum Error {
/// Failed to parse direct parameter.
ParseDirectParam,
/// Failed to parse image parameter.
@@ -96,70 +91,32 @@ impl convert::From<Error> for io::Error {
}
}
pub struct VhostUserBlkBackend {
struct VhostUserBlkThread {
mem: Option<GuestMemoryMmap>,
vring_worker: Option<Arc<VringWorker>>,
disk_image: Box<dyn DiskFile>,
disk_image: Arc<Mutex<dyn DiskFile>>,
disk_image_id: Vec<u8>,
disk_nsectors: u64,
config: virtio_blk_config,
rdonly: bool,
poll_queue: bool,
event_idx: bool,
kill_evt: EventFd,
}
impl VhostUserBlkBackend {
pub fn new(
image_path: String,
num_queues: usize,
rdonly: bool,
direct: bool,
poll_queue: bool,
impl VhostUserBlkThread {
fn new(
disk_image: Arc<Mutex<dyn DiskFile>>,
disk_image_id: Vec<u8>,
disk_nsectors: u64,
) -> Result<Self> {
let mut options = OpenOptions::new();
options.read(true);
options.write(!rdonly);
if direct {
options.custom_flags(libc::O_DIRECT);
}
let image: File = options.open(&image_path).unwrap();
let mut raw_img: vm_virtio::RawFile = vm_virtio::RawFile::new(image, direct);
let image_id = build_disk_image_id(&PathBuf::from(&image_path));
let image_type = qcow::detect_image_type(&mut raw_img).unwrap();
let mut image = match image_type {
ImageType::Raw => Box::new(raw_img) as Box<dyn DiskFile>,
ImageType::Qcow2 => Box::new(QcowFile::from(raw_img).unwrap()) as Box<dyn DiskFile>,
};
let nsectors = (image.seek(SeekFrom::End(0)).unwrap() as u64) / SECTOR_SIZE;
let mut config = virtio_blk_config::default();
config.capacity = nsectors;
config.blk_size = BLK_SIZE;
config.size_max = 65535;
config.seg_max = 128 - 2;
config.min_io_size = 1;
config.opt_io_size = 1;
config.num_queues = num_queues as u16;
config.wce = 1;
Ok(VhostUserBlkBackend {
Ok(VhostUserBlkThread {
mem: None,
vring_worker: None,
disk_image: image,
disk_image_id: image_id,
disk_nsectors: nsectors,
config,
rdonly,
poll_queue,
disk_image,
disk_image_id,
disk_nsectors,
event_idx: false,
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?,
})
}
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 mem = match self.mem.as_ref() {
Some(m) => m,
@@ -173,7 +130,7 @@ impl VhostUserBlkBackend {
Ok(request) => {
debug!("element is a valid request");
let status = match request.execute(
&mut self.disk_image,
&mut self.disk_image.lock().unwrap().deref_mut(),
self.disk_nsectors,
mem,
&self.disk_image_id,
@@ -215,9 +172,73 @@ impl VhostUserBlkBackend {
used_any
}
}
pub fn set_vring_worker(&mut self, vring_worker: Option<Arc<VringWorker>>) {
self.vring_worker = vring_worker;
struct VhostUserBlkBackend {
threads: Vec<Mutex<VhostUserBlkThread>>,
config: virtio_blk_config,
rdonly: bool,
poll_queue: bool,
queues_per_thread: Vec<u64>,
}
impl VhostUserBlkBackend {
fn new(
image_path: String,
num_queues: usize,
rdonly: bool,
direct: bool,
poll_queue: bool,
) -> Result<Self> {
let mut options = OpenOptions::new();
options.read(true);
options.write(!rdonly);
if direct {
options.custom_flags(libc::O_DIRECT);
}
let image: File = options.open(&image_path).unwrap();
let mut raw_img: vm_virtio::RawFile = vm_virtio::RawFile::new(image, direct);
let image_id = build_disk_image_id(&PathBuf::from(&image_path));
let image_type = qcow::detect_image_type(&mut raw_img).unwrap();
let 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 = 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;
let mut queues_per_thread = Vec::new();
let mut threads = Vec::new();
for i in 0..num_queues {
let thread = Mutex::new(VhostUserBlkThread::new(
image.clone(),
image_id.clone(),
nsectors,
)?);
threads.push(thread);
queues_per_thread.push(0b1 << i);
}
Ok(VhostUserBlkBackend {
threads,
config,
rdonly,
poll_queue,
queues_per_thread,
})
}
}
@@ -248,19 +269,24 @@ impl VhostUserBackend for VhostUserBlkBackend {
}
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<()> {
self.mem = Some(mem);
for thread in self.threads.iter() {
thread.lock().unwrap().mem = Some(mem.clone());
}
Ok(())
}
fn handle_event(
&mut self,
&self,
device_event: u16,
evset: epoll::Events,
vrings: &[Arc<RwLock<Vring>>],
thread_id: usize,
) -> VhostUserBackendResult<bool> {
if evset != epoll::Events::EPOLLIN {
return Err(Error::HandleEventNotEpollIn.into());
@@ -268,16 +294,17 @@ impl VhostUserBackend for VhostUserBlkBackend {
debug!("event received: {:?}", device_event);
let mut thread = self.threads[thread_id].lock().unwrap();
match device_event {
q if device_event < self.config.num_queues => {
let mut vring = vrings[q as usize].write().unwrap();
0 => {
let mut vring = vrings[0].write().unwrap();
if self.poll_queue {
// Actively poll the queue until POLL_QUEUE_US has passed
// without seeing a new request.
let mut now = Instant::now();
loop {
if self.process_queue(&mut vring) {
if thread.process_queue(&mut vring) {
now = Instant::now();
} else if now.elapsed().as_micros() > POLL_QUEUE_US {
break;
@@ -285,7 +312,7 @@ impl VhostUserBackend for VhostUserBlkBackend {
}
}
if self.event_idx {
if thread.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
@@ -293,14 +320,14 @@ impl VhostUserBackend for VhostUserBlkBackend {
loop {
vring
.mut_queue()
.update_avail_event(self.mem.as_ref().unwrap());
if !self.process_queue(&mut vring) {
.update_avail_event(thread.mem.as_ref().unwrap());
if !thread.process_queue(&mut vring) {
break;
}
}
} else {
// Without EVENT_IDX, a single call is enough.
self.process_queue(&mut vring);
thread.process_queue(&mut vring);
}
Ok(false)
@@ -321,22 +348,35 @@ impl VhostUserBackend for VhostUserBlkBackend {
buf.to_vec()
}
fn exit_event(&self) -> Option<(EventFd, Option<u16>)> {
Some((self.kill_evt.try_clone().unwrap(), None))
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> {
pub image: &'a str,
pub sock: &'a str,
pub num_queues: usize,
pub readonly: bool,
pub direct: bool,
pub poll_queue: bool,
struct VhostUserBlkBackendConfig<'a> {
image: &'a str,
sock: &'a str,
num_queues: usize,
readonly: bool,
direct: bool,
poll_queue: bool,
}
impl<'a> VhostUserBlkBackendConfig<'a> {
pub fn parse(backend: &'a str) -> Result<Self> {
fn parse(backend: &'a str) -> Result<Self> {
let params_list: Vec<&str> = backend.split(',').collect();
let mut image: &str = "";
@@ -347,10 +387,10 @@ impl<'a> VhostUserBlkBackendConfig<'a> {
let mut poll_queue: bool = true;
for param in params_list.iter() {
if param.starts_with("image=") {
image = &param[6..];
} else if param.starts_with("sock=") {
sock = &param[5..];
if param.starts_with("path=") {
image = &param[5..];
} else if param.starts_with("socket=") {
sock = &param[7..];
} else if param.starts_with("num_queues=") {
num_queues_str = &param[11..];
} else if param.starts_with("readonly=") {
@@ -425,12 +465,6 @@ pub fn start_block_backend(backend_command: &str) {
.unwrap();
debug!("blk_daemon is created!\n");
let vring_worker = blk_daemon.get_vring_worker();
blk_backend
.write()
.unwrap()
.set_vring_worker(Some(vring_worker));
if let Err(e) = blk_daemon.start() {
error!(
"Failed to start daemon for vhost-user-block with error: {:?}\n",
@@ -443,8 +477,9 @@ pub fn start_block_backend(backend_command: &str) {
error!("Error from the main thread: {:?}", e);
}
let kill_evt = &blk_backend.write().unwrap().kill_evt;
if let Err(e) = kill_evt.write(1) {
error!("Error shutting down worker thread: {:?}", e)
for thread in blk_backend.read().unwrap().threads.iter() {
if let Err(e) = thread.lock().unwrap().kill_evt.write(1) {
error!("Error shutting down worker thread: {:?}", e)
}
}
}

View File

@@ -6,8 +6,8 @@ edition = "2018"
[dependencies]
bitflags = "1.1.0"
libc = "0.2.68"
libc = "0.2.69"
log = "0.4.8"
vm-memory = "0.1.0"
vm-memory = "0.2.0"
vm-virtio = { path = "../vm-virtio" }
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }

View File

@@ -682,11 +682,11 @@ pub trait FileSystem {
/// implementation did not return a `Handle` from `open` then the contents of `handle` are
/// 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 `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
/// 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
@@ -702,6 +702,7 @@ pub trait FileSystem {
offset: u64,
lock_owner: Option<u64>,
delayed_write: bool,
kill_priv: bool,
flags: u32,
) -> io::Result<usize> {
Err(io::Error::from_raw_os_error(libc::ENOSYS))
@@ -1096,6 +1097,22 @@ pub trait FileSystem {
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
fn getlk(&self) -> io::Result<()> {
Err(io::Error::from_raw_os_error(libc::ENOSYS))

View File

@@ -42,7 +42,8 @@ impl FsCacheReqHandler for SlaveFsCacheReq {
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<()> {

View File

@@ -55,6 +55,9 @@ const FOPEN_KEEP_CACHE: u32 = 2;
/// The file is not seekable.
const FOPEN_NONSEEKABLE: u32 = 4;
/// Allow caching this directory.
const FOPEN_CACHE_DIR: u32 = 8;
bitflags! {
/// Options controlling the behavior of files opened by the server in response
/// to an open or create request.
@@ -62,6 +65,7 @@ bitflags! {
const DIRECT_IO = FOPEN_DIRECT_IO;
const KEEP_CACHE = FOPEN_KEEP_CACHE;
const NONSEEKABLE = FOPEN_NONSEEKABLE;
const CACHE_DIR = FOPEN_CACHE_DIR;
}
}
@@ -130,6 +134,21 @@ const HANDLE_KILLPRIV: u32 = 524_288;
/// FileSystem supports posix acls.
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! {
/// A bitfield passed in as a parameter to and returned from the `init` method of the
/// `FileSystem` trait.
@@ -296,6 +315,43 @@ bitflags! {
///
/// This feature is disabled by default.
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 +373,9 @@ pub const WRITE_CACHE: u32 = 1;
/// `lock_owner` field is valid.
pub const WRITE_LOCKOWNER: u32 = 2;
/// Kill suid and sgid bits
pub const WRITE_KILL_PRIV: u32 = 4;
// Read flags.
pub const READ_LOCKOWNER: u32 = 2;
@@ -337,6 +396,9 @@ const IOCTL_32BIT: u32 = 8;
/// Is a directory
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
const IOCTL_MAX_IOV: u32 = 256;
@@ -357,6 +419,9 @@ bitflags! {
/// Is a directory
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
const IOCTL_MAX_IOV = IOCTL_MAX_IOV;
}
@@ -835,7 +900,9 @@ pub struct InitOut {
pub congestion_threshold: u16,
pub max_write: 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 {}
@@ -1049,6 +1116,19 @@ pub struct 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! {
pub struct SetupmappingFlags: u64 {
const WRITE = 0x1;

View File

@@ -557,7 +557,13 @@ impl PassthroughFs {
OpenOptions::DIRECT_IO,
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 +997,15 @@ impl FileSystem for PassthroughFs {
offset: u64,
_lock_owner: Option<u64>,
_delayed_write: bool,
kill_priv: bool,
_flags: u32,
) -> io::Result<usize> {
// We need to change credentials during a write so that the kernel will remove setuid 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)?;
if kill_priv {
// We need to change credentials during a write so that the kernel will remove setuid
// 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
.handles
.read()
@@ -1644,4 +1654,60 @@ impl FileSystem for PassthroughFs {
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

@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::convert::TryInto;
use std::ffi::CStr;
use std::fs::File;
use std::io::{self, Read, Write};
@@ -19,7 +20,7 @@ use crate::filesystem::{
use crate::fuse::*;
use crate::{Error, Result};
const MAX_BUFFER_SIZE: u32 = (1 << 20);
const MAX_BUFFER_SIZE: u32 = 1 << 20;
const DIRENT_PADDING: [u8; 8] = [0; 8];
struct ZCReader<'a>(Reader<'a>);
@@ -124,6 +125,7 @@ impl<F: FileSystem + Sync> Server<F> {
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::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::RemoveMapping as u32 => self.removemapping(in_header, r, w, vu_req),
_ => reply_error(
@@ -634,6 +636,7 @@ impl<F: FileSystem + Sync> Server<F> {
};
let delayed_write = write_flags & WRITE_CACHE != 0;
let kill_priv = write_flags & WRITE_KILL_PRIV != 0;
let data_reader = ZCReader(r);
@@ -646,6 +649,7 @@ impl<F: FileSystem + Sync> Server<F> {
offset,
owner,
delayed_write,
kill_priv,
flags,
) {
Ok(count) => {
@@ -902,10 +906,14 @@ impl<F: FileSystem + Sync> Server<F> {
| FsOptions::HANDLE_KILLPRIV
| FsOptions::ASYNC_DIO
| FsOptions::HAS_IOCTL_DIR
| FsOptions::ATOMIC_O_TRUNC;
| FsOptions::ATOMIC_O_TRUNC
| FsOptions::MAX_PAGES;
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) {
Ok(want) => {
let enabled = capable & (want | supported);
@@ -919,6 +927,7 @@ impl<F: FileSystem + Sync> Server<F> {
congestion_threshold: (::std::u16::MAX / 4) * 3,
max_write: MAX_BUFFER_SIZE,
time_gran: 1, // nanoseconds
max_pages: max_pages.try_into().unwrap(),
..Default::default()
};
@@ -1261,6 +1270,41 @@ impl<F: FileSystem + Sync> Server<F> {
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>(

View File

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

View File

@@ -16,14 +16,13 @@ use epoll;
use libc::{self, EAGAIN, EFD_NONBLOCK};
use log::*;
use net_util::Tap;
use std::convert::TryFrom;
use std::fmt;
use std::io::Read;
use std::io::{self};
use std::net::Ipv4Addr;
use std::os::unix::io::AsRawFd;
use std::process;
use std::sync::{Arc, RwLock};
use std::sync::{Arc, Mutex, RwLock};
use std::vec::Vec;
use vhost_rs::vhost_user::message::*;
use vhost_rs::vhost_user::Error as VhostUserError;
@@ -92,104 +91,64 @@ impl std::convert::From<Error> for std::io::Error {
}
}
pub struct VhostUserNetBackend {
struct VhostUserNetThread {
mem: Option<GuestMemoryMmap>,
vring_worker: Option<Arc<VringWorker>>,
kill_evt: EventFd,
taps: Vec<(Tap, usize)>,
rxs: Vec<RxVirtio>,
txs: Vec<TxVirtio>,
rx_tap_listenings: Vec<bool>,
num_queues: usize,
queue_size: u16,
tap: Tap,
rx: RxVirtio,
tx: TxVirtio,
rx_tap_listening: bool,
}
impl VhostUserNetBackend {
impl VhostUserNetThread {
/// Create a new virtio network device with the given TAP interface.
pub fn new_with_tap(taps: Vec<Tap>, num_queues: usize, queue_size: u16) -> Result<Self> {
let mut taps_v: Vec<(Tap, usize)> = Vec::new();
for (i, tap) in taps.iter().enumerate() {
taps_v.push((tap.clone(), num_queues + i));
}
let mut rxs: Vec<RxVirtio> = Vec::new();
let mut txs: Vec<TxVirtio> = Vec::new();
let mut rx_tap_listenings: Vec<bool> = Vec::new();
for _ in 0..taps.len() {
let rx = RxVirtio::new();
rxs.push(rx);
let tx = TxVirtio::new();
txs.push(tx);
rx_tap_listenings.push(false);
}
Ok(VhostUserNetBackend {
fn new(tap: Tap) -> Result<Self> {
Ok(VhostUserNetThread {
mem: None,
vring_worker: None,
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?,
taps: taps_v,
rxs,
txs,
rx_tap_listenings,
num_queues,
queue_size,
tap,
rx: RxVirtio::new(),
tx: TxVirtio::new(),
rx_tap_listening: false,
})
}
/// Create a new virtio network device with the given IP address and
/// netmask.
pub fn new(
ip_addr: Ipv4Addr,
netmask: Ipv4Addr,
num_queues: usize,
queue_size: u16,
ifname: Option<&str>,
) -> Result<Self> {
let taps = open_tap(ifname, Some(ip_addr), Some(netmask), num_queues / 2)
.map_err(Error::OpenTap)?;
Self::new_with_tap(taps, num_queues, queue_size)
}
// Copies a single frame from `self.rx.frame_buf` into the guest. Returns true
// if a buffer was used, and false if the frame must be deferred until a buffer
// is made available by the driver.
fn rx_single_frame(&mut self, mut queue: &mut Queue, index: usize) -> Result<bool> {
fn rx_single_frame(&mut self, mut queue: &mut Queue) -> 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] {
if self.rx_tap_listening {
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(),
)
.unregister_listener(self.tap.as_raw_fd(), epoll::Events::EPOLLIN, 2)
.unwrap();
self.rx_tap_listenings[index] = false;
self.rx_tap_listening = false;
}
return Ok(false);
}
let write_complete = self.rxs[index].process_desc_chain(&mem, next_desc, &mut queue);
let write_complete = self.rx.process_desc_chain(&mem, next_desc, &mut queue);
Ok(write_complete)
}
fn process_rx(&mut self, vring: &mut Vring, index: usize) -> Result<()> {
fn process_rx(&mut self, vring: &mut Vring) -> Result<()> {
// Read as many frames as possible.
loop {
match self.read_tap(index) {
match self.read_tap() {
Ok(count) => {
self.rxs[index].bytes_read = count;
if !self.rx_single_frame(&mut vring.mut_queue(), index)? {
self.rxs[index].deferred_frame = true;
self.rx.bytes_read = count;
if !self.rx_single_frame(&mut vring.mut_queue())? {
self.rx.deferred_frame = true;
break;
}
}
@@ -207,8 +166,8 @@ impl VhostUserNetBackend {
}
}
}
if self.rxs[index].deferred_irqs {
self.rxs[index].deferred_irqs = false;
if self.rx.deferred_irqs {
self.rx.deferred_irqs = false;
vring.signal_used_queue().unwrap();
Ok(())
} else {
@@ -216,15 +175,15 @@ impl VhostUserNetBackend {
}
}
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;
fn resume_rx(&mut self, vring: &mut Vring) -> Result<()> {
if self.rx.deferred_frame {
if self.rx_single_frame(&mut vring.mut_queue())? {
self.rx.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;
self.process_rx(vring)
} else if self.rx.deferred_irqs {
self.rx.deferred_irqs = false;
vring.signal_used_queue().unwrap();
Ok(())
} else {
@@ -235,16 +194,16 @@ impl VhostUserNetBackend {
}
}
fn process_tx(&mut self, mut queue: &mut Queue, index: usize) -> Result<()> {
fn process_tx(&mut self, mut queue: &mut Queue) -> 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);
self.tx.process_desc_chain(&mem, &mut self.tap, &mut queue);
Ok(())
}
fn read_tap(&mut self, index: usize) -> io::Result<usize> {
self.taps[index].0.read(&mut self.rxs[index].frame_buf)
fn read_tap(&mut self) -> io::Result<usize> {
self.tap.read(&mut self.rx.frame_buf)
}
pub fn set_vring_worker(&mut self, vring_worker: Option<Arc<VringWorker>>) {
@@ -252,6 +211,41 @@ impl VhostUserNetBackend {
}
}
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,
netmask: Ipv4Addr,
num_queues: usize,
queue_size: u16,
ifname: Option<&str>,
) -> Result<Self> {
let mut taps = open_tap(ifname, Some(ip_addr), Some(netmask), num_queues / 2)
.map_err(Error::OpenTap)?;
let mut queues_per_thread = Vec::new();
let mut threads = Vec::new();
for (i, tap) in taps.drain(..).enumerate() {
let thread = Mutex::new(VhostUserNetThread::new(tap)?);
threads.push(thread);
queues_per_thread.push(0b11 << (i * 2));
}
Ok(VhostUserNetBackend {
threads,
num_queues,
queue_size,
queues_per_thread,
})
}
}
impl VhostUserBackend for VhostUserNetBackend {
fn num_queues(&self) -> usize {
self.num_queues
@@ -279,59 +273,55 @@ impl VhostUserBackend for VhostUserNetBackend {
fn set_event_idx(&mut self, _enabled: bool) {}
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(())
}
fn handle_event(
&mut self,
&self,
device_event: u16,
evset: epoll::Events,
vrings: &[Arc<RwLock<Vring>>],
thread_id: usize,
) -> VhostUserBackendResult<bool> {
if evset != epoll::Events::EPOLLIN {
return Err(Error::HandleEventNotEpollIn.into());
}
let tap_start_index = self.num_queues as u16;
let tap_end_index = (self.num_queues + self.num_queues / 2 - 1) as u16;
let mut thread = self.threads[thread_id].lock().unwrap();
match device_event {
x if ((x < self.num_queues as u16) && (x % 2 == 0)) => {
let index = (x / 2) as usize;
let mut vring = vrings[x as usize].write().unwrap();
self.resume_rx(&mut vring, index)?;
0 => {
thread.resume_rx(&mut vrings[0].write().unwrap())?;
if !self.rx_tap_listenings[index] {
self.vring_worker.as_ref().unwrap().register_listener(
self.taps[index].0.as_raw_fd(),
if !thread.rx_tap_listening {
thread.vring_worker.as_ref().unwrap().register_listener(
thread.tap.as_raw_fd(),
epoll::Events::EPOLLIN,
u64::try_from(self.taps[index].1).unwrap(),
2,
)?;
self.rx_tap_listenings[index] = true;
thread.rx_tap_listening = true;
}
}
x if ((x < self.num_queues as u16) && (x % 2 != 0)) => {
let index = ((x - 1) / 2) as usize;
let mut vring = vrings[x as usize].write().unwrap();
self.process_tx(&mut vring.mut_queue(), index)?;
1 => {
thread.process_tx(&mut vrings[1].write().unwrap().mut_queue())?;
}
x if x >= tap_start_index && x <= tap_end_index => {
let index = x as usize - self.num_queues;
let mut vring = vrings[2 * index].write().unwrap();
if self.rxs[index].deferred_frame
2 => {
let mut vring = vrings[0].write().unwrap();
if thread.rx.deferred_frame
// Process a deferred frame first if available. Don't read from tap again
// until we manage to receive this deferred frame.
{
if self.rx_single_frame(&mut vring.mut_queue(), index)? {
self.rxs[index].deferred_frame = false;
self.process_rx(&mut vring, index)?;
} else if self.rxs[index].deferred_irqs {
self.rxs[index].deferred_irqs = false;
if thread.rx_single_frame(&mut vring.mut_queue())? {
thread.rx.deferred_frame = false;
thread.process_rx(&mut vring)?;
} else if thread.rx.deferred_irqs {
thread.rx.deferred_irqs = false;
vring.signal_used_queue()?;
}
} else {
self.process_rx(&mut vring, index)?;
thread.process_rx(&mut vring)?;
}
}
_ => return Err(Error::HandleEventUnknownEvent.into()),
@@ -340,10 +330,22 @@ impl VhostUserBackend for VhostUserNetBackend {
Ok(false)
}
fn exit_event(&self) -> Option<(EventFd, Option<u16>)> {
let tap_end_index = (self.num_queues + self.num_queues / 2 - 1) as u16;
let kill_index = tap_end_index + 1;
Some((self.kill_evt.try_clone().unwrap(), Some(kill_index)))
fn exit_event(&self, thread_index: usize) -> Option<(EventFd, Option<u16>)> {
// The exit event is placed after the queues and the tap event, which
// is event index 3.
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()
}
}
@@ -423,7 +425,7 @@ pub fn start_net_backend(backend_command: &str) {
let backend_config = match VhostUserNetBackendConfig::parse(backend_command) {
Ok(config) => config,
Err(e) => {
println!("Failed parsing parameters {:?}", e);
eprintln!("Failed parsing parameters {:?}", e);
process::exit(1);
}
};
@@ -446,15 +448,22 @@ pub fn start_net_backend(backend_command: &str) {
)
.unwrap();
let vring_worker = net_daemon.get_vring_worker();
let mut vring_workers = net_daemon.get_vring_workers();
net_backend
.write()
.unwrap()
.set_vring_worker(Some(vring_worker));
if vring_workers.len() != net_backend.read().unwrap().threads.len() {
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() {
println!(
error!(
"failed to start daemon for vhost-user-net with error: {:?}",
e
);
@@ -465,8 +474,9 @@ pub fn start_net_backend(backend_command: &str) {
error!("Error from the main thread: {:?}", e);
}
let kill_evt = &net_backend.write().unwrap().kill_evt;
if let Err(e) = kill_evt.write(1) {
error!("Error shutting down worker thread: {:?}", e)
for thread in net_backend.read().unwrap().threads.iter() {
if let Err(e) = thread.lock().unwrap().kill_evt.write(1) {
error!("Error shutting down worker thread: {:?}", e)
}
}
}

View File

@@ -5,5 +5,5 @@ authors = ["The Chromium OS Authors"]
edition = "2018"
[dependencies]
libc = "0.2.68"
vm-memory = "0.1.0"
libc = "0.2.69"
vm-memory = "0.2.0"

View File

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

View File

@@ -1,5 +1,3 @@
extern crate serde;
extern crate thiserror;
extern crate vm_memory;
pub mod interrupt;
@@ -9,8 +7,6 @@ use vm_memory::{
MemoryRegionAddress,
};
use thiserror::Error;
/// Trait meant for triggering the DMA mapping update related to an external
/// device not managed fully through virtio. It is dedicated to virtio-iommu
/// in order to trigger the map update anytime the mapping is updated from the
@@ -23,34 +19,6 @@ pub trait ExternalDmaMapping: Send + Sync {
fn unmap(&self, iova: u64, size: u64) -> std::result::Result<(), std::io::Error>;
}
#[derive(Error, Debug)]
pub enum MigratableError {
#[error("Failed to pause migratable component: {0}")]
Pause(#[source] anyhow::Error),
#[error("Failed to resume migratable component: {0}")]
Resume(#[source] anyhow::Error),
}
/// A Pausable component can be paused and resumed.
pub trait Pausable {
/// Pause the component.
fn pause(&mut self) -> std::result::Result<(), MigratableError>;
/// Resume the component.
fn resume(&mut self) -> std::result::Result<(), MigratableError>;
}
/// A snapshotable component can be snapshoted.
pub trait Snapshotable {}
/// Trait to be implemented by any component (device, CPU, RAM, etc) that
/// can be migrated.
/// All migratable components are paused before being snapshotted, and then
/// eventually resumed. Thus any Migratable component must be both Pausable
/// and Snapshotable.
pub trait Migratable: Send + Pausable + Snapshotable {}
fn get_region_host_address_range(
region: &GuestRegionMmap,
addr: MemoryRegionAddress,

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,24 @@ pci_support = ["pci"]
mmio_support = []
[dependencies]
anyhow = "1.0"
arc-swap = ">=0.4.4"
byteorder = "1.3.4"
devices = { path = "../devices" }
epoll = ">=4.0.1"
libc = "0.2.68"
libc = "0.2.69"
log = "0.4.8"
net_gen = { path = "../net_gen" }
net_util = { path = "../net_util" }
pci = { path = "../pci", optional = true }
serde = ">=1.0.27"
serde_derive = ">=1.0.27"
serde_json = ">=1.0.9"
tempfile = "3.1.0"
virtio-bindings = { git = "https://github.com/rust-vmm/virtio-bindings", version = "0.1", features = ["virtio-v5_0_0"]}
vm-allocator = { path = "../vm-allocator" }
vm-device = { path = "../vm-device" }
vm-memory = { version = "0.1.0", features = ["backend-mmap", "backend-atomic"] }
vm-memory = { version = "0.2.0", features = ["backend-mmap", "backend-atomic"] }
vm-migration = { path = "../vm-migration" }
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"] }

View File

@@ -14,8 +14,10 @@ use super::{
VirtioDeviceType, VirtioInterruptType,
};
use crate::VirtioInterrupt;
use anyhow::anyhow;
use epoll;
use libc::{c_void, EFD_NONBLOCK};
use serde::ser::{Serialize, SerializeStruct, Serializer};
use std::alloc::{alloc_zeroed, dealloc, Layout};
use std::cmp;
use std::convert::TryInto;
@@ -31,11 +33,14 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::thread;
use virtio_bindings::bindings::virtio_blk::*;
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
use vm_memory::{
ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
GuestMemoryError, GuestMemoryMmap,
};
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
Transportable,
};
use vmm_sys_util::{eventfd::EventFd, seek_hole::SeekHole, write_zeroes::PunchHole};
const SECTOR_SHIFT: u8 = 9;
@@ -766,7 +771,7 @@ impl<T: DiskFile> BlockEpollHandler<T> {
}
}
#[derive(Copy, Clone, Debug, Default)]
#[derive(Copy, Clone, Debug, Default, Deserialize)]
#[repr(C, packed)]
pub struct VirtioBlockGeometry {
pub cylinders: u16,
@@ -774,9 +779,31 @@ pub struct VirtioBlockGeometry {
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 {}
#[derive(Copy, Clone, Debug, Default)]
#[derive(Copy, Clone, Debug, Default, Deserialize)]
#[repr(C, packed)]
pub struct VirtioBlockConfig {
pub capacity: u64,
@@ -800,10 +827,67 @@ pub struct VirtioBlockConfig {
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 wce = self.wce;
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("wce", &wce)?;
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 {}
/// Virtio device for exposing block level read/write operations on a host file.
pub struct Block<T: DiskFile> {
id: String,
kill_evt: Option<EventFd>,
disk_image: Arc<Mutex<T>>,
disk_path: PathBuf,
@@ -819,11 +903,21 @@ pub struct Block<T: DiskFile> {
queue_size: Vec<u16>,
}
#[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> {
/// Create a new virtio block device that operates on the given file.
///
/// The given file must be seekable and sizable.
pub fn new(
id: String,
mut disk_image: T,
disk_path: PathBuf,
is_disk_read_only: bool,
@@ -862,6 +956,7 @@ impl<T: DiskFile> Block<T> {
}
Ok(Block {
id,
kill_evt: None,
disk_image: Arc::new(Mutex::new(disk_image)),
disk_path,
@@ -877,6 +972,26 @@ impl<T: DiskFile> Block<T> {
queue_size: vec![queue_size; num_queues],
})
}
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(())
}
}
impl<T: DiskFile> Drop for Block<T> {
@@ -1051,5 +1166,45 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
}
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> {}

View File

@@ -7,8 +7,10 @@ use super::{
VirtioInterruptType, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
};
use crate::VirtioInterrupt;
use anyhow::anyhow;
use epoll;
use libc::EFD_NONBLOCK;
use serde::ser::{Serialize, SerializeStruct, Serializer};
use std;
use std::cmp;
use std::collections::VecDeque;
@@ -20,8 +22,11 @@ use std::result;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::thread;
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
use vm_memory::{ByteValued, Bytes, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
Transportable,
};
use vmm_sys_util::eventfd::EventFd;
const QUEUE_SIZE: u16 = 256;
@@ -43,8 +48,8 @@ const PAUSE_EVENT: DeviceEventT = 5;
//Console size feature bit
const VIRTIO_CONSOLE_F_SIZE: u64 = 0;
#[derive(Copy, Clone, Debug, Default)]
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, Deserialize)]
#[repr(C, packed)]
pub struct VirtioConsoleConfig {
cols: u16,
rows: u16,
@@ -52,6 +57,30 @@ pub struct VirtioConsoleConfig {
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.
unsafe impl ByteValued for VirtioConsoleConfig {}
@@ -343,6 +372,7 @@ impl VirtioConsoleConfig {
/// Virtio device for exposing console to the guest OS through virtio.
pub struct Console {
id: String,
kill_evt: Option<EventFd>,
pause_evt: Option<EventFd>,
avail_features: u64,
@@ -356,9 +386,18 @@ pub struct Console {
paused: Arc<AtomicBool>,
}
#[derive(Serialize, Deserialize)]
pub struct ConsoleState {
avail_features: u64,
acked_features: u64,
config: VirtioConsoleConfig,
in_buffer: VecDeque<u8>,
}
impl Console {
/// Create a new virtio console device that gets random data from /dev/urandom.
pub fn new(
id: String,
out: Box<dyn io::Write + Send + Sync + 'static>,
cols: u16,
rows: u16,
@@ -383,6 +422,7 @@ impl Console {
Ok((
Console {
id,
kill_evt: None,
pause_evt: None,
avail_features,
@@ -398,6 +438,24 @@ impl Console {
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 {
@@ -563,5 +621,45 @@ impl VirtioDevice for 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 {}

View File

@@ -6,7 +6,7 @@
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use super::*;
use crate::{ActivateResult, Error, Queue};
use std::sync::Arc;
use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestMemoryMmap, GuestUsize};
use vmm_sys_util::eventfd::EventFd;
@@ -34,6 +34,15 @@ pub trait VirtioInterrupt: Send + Sync {
pub type VirtioIommuRemapping =
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)]
pub struct VirtioSharedMemory {
pub offset: u64,
@@ -42,6 +51,8 @@ pub struct VirtioSharedMemory {
#[derive(Clone)]
pub struct VirtioSharedMemoryList {
pub host_addr: u64,
pub mem_slot: u32,
pub addr: GuestAddress,
pub len: GuestUsize,
pub region_list: Vec<VirtioSharedMemory>,
@@ -97,6 +108,14 @@ pub trait VirtioDevice: Send {
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 {
addr
}
@@ -106,6 +125,15 @@ pub trait VirtioDevice: Send {
/// every device as part of shutting down the VM. Acting on the device
/// after a shutdown() can lead to unpredictable results.
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

View File

@@ -21,11 +21,12 @@ use std::result;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock};
use std::thread;
use vm_device::{ExternalDmaMapping, Migratable, MigratableError, Pausable, Snapshotable};
use vm_device::ExternalDmaMapping;
use vm_memory::{
Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic,
GuestMemoryError, GuestMemoryMmap,
};
use vm_migration::{Migratable, MigratableError, Pausable, Snapshottable, Transportable};
use vmm_sys_util::eventfd::EventFd;
/// Queues sizes
@@ -792,6 +793,7 @@ impl DmaRemapping for IommuMapping {
}
pub struct Iommu {
id: String,
kill_evt: Option<EventFd>,
pause_evt: Option<EventFd>,
avail_features: u64,
@@ -807,7 +809,7 @@ pub struct Iommu {
}
impl Iommu {
pub fn new() -> io::Result<(Self, Arc<IommuMapping>)> {
pub fn new(id: String) -> io::Result<(Self, Arc<IommuMapping>)> {
let config = VirtioIommuConfig {
page_size_mask: VIRTIO_IOMMU_PAGE_SIZE_MASK,
probe_size: PROBE_PROP_SIZE,
@@ -821,6 +823,7 @@ impl Iommu {
Ok((
Iommu {
id,
kill_evt: None,
pause_evt: None,
avail_features: 1u64 << VIRTIO_F_VERSION_1
@@ -1038,5 +1041,10 @@ impl VirtioDevice for Iommu {
}
virtio_pausable!(Iommu);
impl Snapshotable for Iommu {}
impl Snapshottable for Iommu {
fn id(&self) -> String {
self.id.clone()
}
}
impl Transportable for Iommu {}
impl Migratable for Iommu {}

View File

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

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

@@ -0,0 +1,966 @@
// 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 epoll;
use libc;
use libc::EFD_NONBLOCK;
use std::cmp;
use std::io::{self, Write};
use std::mem::size_of;
use std::os::unix::io::{AsRawFd, 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>) {
self.tx.send(r).unwrap();
}
}
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)?;
// Add events
epoll::ctl(
epoll_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_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_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_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_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() {
error!("Failed to get resize event: {:?}", e);
break 'epoll;
} else {
let size = self.resize.get_size();
let mut config = self.config.lock().unwrap();
let mut need_break = 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) {
need_break = true;
Err(Error::ResizeTriggerFail(e))
} else {
Ok(())
}
};
if let Err(e) = &r {
error!("{:?}", e);
}
self.resize.send(r);
if need_break {
break 'epoll;
}
}
}
QUEUE_AVAIL_EVENT => {
if let Err(e) = self.queue_evt.read() {
error!("Failed to get queue event: {:?}", e);
break 'epoll;
} else if self.process_queue() {
if let Err(e) = self.signal(&VirtioInterruptType::Queue) {
error!("Failed to signal used queue: {:?}", e);
break 'epoll;
}
}
}
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();
}
}
_ => {
error!("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,6 +15,7 @@ use super::{
ActivateError, ActivateResult, Queue, VirtioDevice, VirtioDeviceType, VirtioInterruptType,
};
use crate::VirtioInterrupt;
use anyhow::anyhow;
use epoll;
use libc::EAGAIN;
use libc::EFD_NONBLOCK;
@@ -30,8 +31,11 @@ use std::sync::Arc;
use std::thread;
use std::vec::Vec;
use virtio_bindings::bindings::virtio_net::*;
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
use vm_memory::{ByteValued, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
Transportable,
};
use vmm_sys_util::eventfd::EventFd;
#[derive(Debug)]
@@ -234,6 +238,19 @@ impl NetEpollHandler {
)
.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.mem.memory()).unwrap() {
epoll::ctl(
self.epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.tap.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_TAP_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
self.rx_tap_listening = true;
}
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); NET_EVENTS_COUNT];
'epoll: loop {
@@ -275,6 +292,7 @@ impl NetEpollHandler {
// Drain pause event
let _ = self.pause_evt.read();
debug!("PAUSE_EVENT received, pausing virtio-net epoll loop");
// We loop here to handle spurious park() returns.
// Until we have not resumed, the paused boolean will
// be true.
@@ -293,6 +311,7 @@ impl NetEpollHandler {
}
pub struct Net {
id: String,
kill_evt: Option<EventFd>,
pause_evt: Option<EventFd>,
taps: Option<Vec<Tap>>,
@@ -307,9 +326,18 @@ pub struct Net {
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 {
/// Create a new virtio network device with the given TAP interface.
pub fn new_with_tap(
id: String,
taps: Vec<Tap>,
guest_mac: Option<MacAddr>,
iommu: bool,
@@ -339,6 +367,7 @@ impl Net {
}
Ok(Net {
id,
kill_evt: None,
pause_evt: None,
taps: Some(taps),
@@ -356,7 +385,9 @@ impl Net {
/// Create a new virtio network device with the given IP address and
/// netmask.
#[allow(clippy::too_many_arguments)]
pub fn new(
id: String,
if_name: Option<&str>,
ip_addr: Option<Ipv4Addr>,
netmask: Option<Ipv4Addr>,
@@ -367,7 +398,25 @@ impl Net {
) -> Result<Self> {
let taps = open_tap(if_name, ip_addr, netmask, 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(())
}
}
@@ -567,5 +616,45 @@ impl VirtioDevice for 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 {}

View File

@@ -5,6 +5,7 @@
use super::Error as DeviceError;
use super::{DescriptorChain, DeviceEventT, Queue};
use net_util::{MacAddr, Tap, TapError};
use serde::ser::{Serialize, SerializeStruct, Serializer};
use std::cmp;
use std::fs;
use std::io::{self, Write};
@@ -47,7 +48,7 @@ const CTRL_QUEUE_EVENT: DeviceEventT = 0;
const CTRL_EVENT_COUNT: usize = 3;
#[repr(C, packed)]
#[derive(Copy, Clone, Debug, Default)]
#[derive(Copy, Clone, Debug, Default, Deserialize)]
pub struct VirtioNetConfig {
pub mac: [u8; 6],
pub status: u16,
@@ -57,6 +58,34 @@ pub struct VirtioNetConfig {
pub duplex: 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 VirtioNetConfig {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
let mac = self.mac;
let status = self.status;
let max_virtqueue_pairs = self.max_virtqueue_pairs;
let mtu = self.mtu;
let speed = self.speed;
let duplex = self.duplex;
let mut virtio_net_config = serializer.serialize_struct("VirtioNetConfig", 17)?;
virtio_net_config.serialize_field("mac", &mac)?;
virtio_net_config.serialize_field("status", &status)?;
virtio_net_config.serialize_field("max_virtqueue_pairs", &max_virtqueue_pairs)?;
virtio_net_config.serialize_field("mtu", &mtu)?;
virtio_net_config.serialize_field("speed", &speed)?;
virtio_net_config.serialize_field("duplex", &duplex)?;
virtio_net_config.end()
}
}
// Safe because it only has data and has no implicit padding.
unsafe impl ByteValued for VirtioNetConfig {}
@@ -430,7 +459,15 @@ impl RxVirtio {
// Mark that we have at least one pending packet and we need to interrupt the guest.
self.deferred_irqs = true;
write_count >= self.bytes_read
// Update the frame_buf buffer.
if write_count < self.bytes_read {
self.frame_buf.copy_within(write_count..self.bytes_read, 0);
self.bytes_read -= write_count;
false
} else {
self.bytes_read = 0;
true
}
}
}

View File

@@ -8,12 +8,14 @@
use super::Error as DeviceError;
use super::{
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice,
VirtioDeviceType, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, UserspaceMapping,
VirtioDevice, VirtioDeviceType, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
};
use crate::{VirtioInterrupt, VirtioInterruptType};
use anyhow::anyhow;
use epoll;
use libc::EFD_NONBLOCK;
use serde::ser::{Serialize, SerializeStruct, Serializer};
use std::cmp;
use std::fmt::{self, Display};
use std::fs::File;
@@ -24,10 +26,13 @@ use std::result;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
use vm_memory::{
Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic,
GuestMemoryError, GuestMemoryMmap, GuestUsize,
GuestMemoryError, GuestMemoryMmap, MmapRegion,
};
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
Transportable,
};
use vmm_sys_util::eventfd::EventFd;
@@ -46,13 +51,33 @@ const KILL_EVENT: DeviceEventT = 1;
// The device should be paused.
const PAUSE_EVENT: DeviceEventT = 2;
#[derive(Copy, Clone, Debug, Default)]
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, Deserialize)]
#[repr(C, packed)]
struct VirtioPmemConfig {
start: u64,
size: u64,
}
// 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 VirtioPmemConfig {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let start = self.start;
let size = self.size;
let mut virtio_pmem_config = serializer.serialize_struct("VirtioPmemConfig", 16)?;
virtio_pmem_config.serialize_field("start", &start)?;
virtio_pmem_config.serialize_field("size", &size)?;
virtio_pmem_config.end()
}
}
// Safe because it only has data and has no implicit padding.
unsafe impl ByteValued for VirtioPmemConfig {}
@@ -312,6 +337,7 @@ impl PmemEpollHandler {
}
pub struct Pmem {
id: String,
kill_evt: Option<EventFd>,
pause_evt: Option<EventFd>,
disk: Option<File>,
@@ -322,13 +348,32 @@ pub struct Pmem {
interrupt_cb: Option<Arc<dyn VirtioInterrupt>>,
epoll_threads: Option<Vec<thread::JoinHandle<result::Result<(), DeviceError>>>>,
paused: Arc<AtomicBool>,
mapping: UserspaceMapping,
// Hold ownership of the memory that is allocated for the device
// which will be automatically dropped when the device is dropped
_region: MmapRegion,
}
#[derive(Serialize, Deserialize)]
pub struct PmemState {
avail_features: u64,
acked_features: u64,
config: VirtioPmemConfig,
}
impl Pmem {
pub fn new(disk: File, addr: GuestAddress, size: GuestUsize, iommu: bool) -> io::Result<Pmem> {
pub fn new(
id: String,
disk: File,
addr: GuestAddress,
mapping: UserspaceMapping,
_region: MmapRegion,
iommu: bool,
) -> io::Result<Pmem> {
let config = VirtioPmemConfig {
start: addr.raw_value().to_le(),
size: size.to_le(),
size: (_region.size() as u64).to_le(),
};
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1;
@@ -338,6 +383,7 @@ impl Pmem {
}
Ok(Pmem {
id,
kill_evt: None,
pause_evt: None,
disk: Some(disk),
@@ -348,8 +394,26 @@ impl Pmem {
interrupt_cb: None,
epoll_threads: None,
paused: Arc::new(AtomicBool::new(false)),
mapping,
_region,
})
}
fn state(&self) -> PmemState {
PmemState {
avail_features: self.avail_features,
acked_features: self.acked_features,
config: self.config,
}
}
fn set_state(&mut self, state: &PmemState) -> io::Result<()> {
self.avail_features = state.avail_features;
self.acked_features = state.acked_features;
self.config = state.config;
Ok(())
}
}
impl Drop for Pmem {
@@ -503,8 +567,53 @@ impl VirtioDevice for Pmem {
self.queue_evts.take().unwrap(),
))
}
fn userspace_mappings(&self) -> Vec<UserspaceMapping> {
vec![self.mapping.clone()]
}
}
virtio_pausable!(Pmem);
impl Snapshotable for Pmem {}
impl Snapshottable for Pmem {
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 pmem_snapshot = Snapshot::new(self.id.as_str());
pmem_snapshot.add_data_section(SnapshotDataSection {
id: format!("{}-section", self.id),
snapshot,
});
Ok(pmem_snapshot)
}
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
if let Some(pmem_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
let pmem_state = match serde_json::from_slice(&pmem_section.snapshot) {
Ok(state) => state,
Err(error) => {
return Err(MigratableError::Restore(anyhow!(
"Could not deserialize PMEM {}",
error
)))
}
};
return self.set_state(&pmem_state).map_err(|e| {
MigratableError::Restore(anyhow!("Could not restore PMEM state {:?}", e))
});
}
Err(MigratableError::Restore(anyhow!(
"Could not find PMEM snapshot section"
)))
}
}
impl Transportable for Pmem {}
impl Migratable for Pmem {}

View File

@@ -17,7 +17,8 @@ use std::sync::Arc;
use crate::device::VirtioIommuRemapping;
use vm_memory::{
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap, GuestUsize,
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap,
GuestUsize,
};
pub(super) const VIRTQ_DESC_F_NEXT: u16 = 0x1;
@@ -29,6 +30,8 @@ pub enum Error {
GuestMemoryError,
InvalidIndirectDescriptor,
InvalidChain,
InvalidOffset(u64),
InvalidRingIndexFromMemory(GuestMemoryError),
}
impl Display for Error {
@@ -39,6 +42,8 @@ impl Display for Error {
GuestMemoryError => write!(f, "error accessing guest memory"),
InvalidChain => write!(f, "invalid descriptor chain"),
InvalidIndirectDescriptor => write!(f, "invalid indirect descriptor"),
InvalidOffset(o) => write!(f, "invalid offset {}", o),
InvalidRingIndexFromMemory(e) => write!(f, "invalid ring index from memory: {}", e),
}
}
}
@@ -383,11 +388,15 @@ impl<'a, 'b> Iterator for AvailIter<'a, 'b> {
}
}
#[derive(Clone)]
#[derive(Serialize, Deserialize)]
#[serde(remote = "GuestAddress")]
struct GuestAddressDef(pub u64);
#[derive(Clone, Serialize, Deserialize)]
/// A virtio queue's parameters.
pub struct Queue {
/// The maximal size in elements offered by the device
max_size: u16,
pub max_size: u16,
/// The queue size in elements the driver selected
pub size: u16,
@@ -398,18 +407,22 @@ pub struct Queue {
/// Interrupt vector index of the queue
pub vector: u16,
#[serde(with = "GuestAddressDef")]
/// Guest physical address of the descriptor table
pub desc_table: GuestAddress,
#[serde(with = "GuestAddressDef")]
/// Guest physical address of the available ring
pub avail_ring: GuestAddress,
#[serde(with = "GuestAddressDef")]
/// Guest physical address of the used ring
pub used_ring: GuestAddress,
pub next_avail: Wrapping<u16>,
pub next_used: Wrapping<u16>,
#[serde(skip)]
pub iommu_mapping_cb: Option<Arc<VirtioIommuRemapping>>,
}
@@ -640,6 +653,29 @@ impl Queue {
pub fn go_to_previous_position(&mut self) {
self.next_avail -= Wrapping(1);
}
/// Get ring's index from memory.
fn index_from_memory(&self, ring: GuestAddress, mem: &GuestMemoryMmap) -> Result<u16, Error> {
mem.read_obj::<u16>(
mem.checked_offset(ring, 2)
.ok_or_else(|| Error::InvalidOffset(ring.raw_value() + 2))?,
)
.map_err(Error::InvalidRingIndexFromMemory)
}
/// Get latest index from available ring.
pub fn avail_index_from_memory(&self, mem: &GuestMemoryMmap) -> Result<u16, Error> {
self.index_from_memory(self.avail_ring, mem)
}
/// Get latest index from used ring.
pub fn used_index_from_memory(&self, mem: &GuestMemoryMmap) -> Result<u16, Error> {
self.index_from_memory(self.used_ring, mem)
}
pub fn available_descriptors(&self, mem: &GuestMemoryMmap) -> Result<bool, Error> {
Ok(self.used_index_from_memory(mem)? < self.avail_index_from_memory(mem)?)
}
}
#[cfg(test)]

View File

@@ -8,6 +8,7 @@ use super::{
VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
};
use crate::{VirtioInterrupt, VirtioInterruptType};
use anyhow::anyhow;
use epoll;
use libc::EFD_NONBLOCK;
use std;
@@ -18,8 +19,11 @@ use std::result;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
use vm_memory::{Bytes, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
Transportable,
};
use vmm_sys_util::eventfd::EventFd;
const QUEUE_SIZE: u16 = 256;
@@ -178,6 +182,7 @@ impl RngEpollHandler {
/// Virtio device for exposing entropy to the guest OS through virtio.
pub struct Rng {
id: String,
kill_evt: Option<EventFd>,
pause_evt: Option<EventFd>,
random_file: Option<File>,
@@ -189,9 +194,16 @@ pub struct Rng {
paused: Arc<AtomicBool>,
}
#[derive(Serialize, Deserialize)]
pub struct RngState {
pub avail_features: u64,
pub acked_features: u64,
pub paused: Arc<AtomicBool>,
}
impl Rng {
/// Create a new virtio rng device that gets random data from /dev/urandom.
pub fn new(path: &str, iommu: bool) -> io::Result<Rng> {
pub fn new(id: String, path: &str, iommu: bool) -> io::Result<Rng> {
let random_file = File::open(path)?;
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1;
@@ -200,6 +212,7 @@ impl Rng {
}
Ok(Rng {
id,
kill_evt: None,
pause_evt: None,
random_file: Some(random_file),
@@ -211,6 +224,22 @@ impl Rng {
paused: Arc::new(AtomicBool::new(false)),
})
}
fn state(&self) -> RngState {
RngState {
avail_features: self.avail_features,
acked_features: self.acked_features,
paused: self.paused.clone(),
}
}
fn set_state(&mut self, state: &RngState) -> io::Result<()> {
self.avail_features = state.avail_features;
self.acked_features = state.acked_features;
self.paused = state.paused.clone();
Ok(())
}
}
impl Drop for Rng {
@@ -357,5 +386,46 @@ impl VirtioDevice for Rng {
}
virtio_pausable!(Rng);
impl Snapshotable for Rng {}
impl Snapshottable for Rng {
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 rng_snapshot = Snapshot::new(self.id.as_str());
rng_snapshot.add_data_section(SnapshotDataSection {
id: format!("{}-section", self.id),
snapshot,
});
Ok(rng_snapshot)
}
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
if let Some(rng_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
let rng_state = match serde_json::from_slice(&rng_section.snapshot) {
Ok(state) => state,
Err(error) => {
return Err(MigratableError::Restore(anyhow!(
"Could not deserialize RNG {}",
error
)))
}
};
return self.set_state(&rng_state).map_err(|e| {
MigratableError::Restore(anyhow!("Could not restore RNG state {:?}", e))
});
}
Err(MigratableError::Restore(anyhow!(
"Could not find RNG snapshot section"
)))
}
}
impl Transportable for Rng {}
impl Migratable for Rng {}

View File

@@ -8,15 +8,20 @@ use crate::{
DEVICE_DRIVER_OK, DEVICE_FAILED, DEVICE_FEATURES_OK, DEVICE_INIT,
INTERRUPT_STATUS_CONFIG_CHANGED, INTERRUPT_STATUS_USED_RING,
};
use anyhow::anyhow;
use byteorder::{ByteOrder, LittleEndian};
use devices::BusDevice;
use libc::EFD_NONBLOCK;
use std::num::Wrapping;
use std::result;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use vm_device::interrupt::InterruptSourceGroup;
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
use vm_memory::{GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
Transportable,
};
use vmm_sys_util::{errno::Result, eventfd::EventFd};
const VENDOR_ID: u32 = 0;
@@ -24,6 +29,12 @@ const VENDOR_ID: u32 = 0;
const MMIO_MAGIC_VALUE: u32 = 0x7472_6976;
const MMIO_VERSION: u32 = 2;
#[derive(Debug)]
enum Error {
/// Failed to retrieve queue ring's index.
QueueRingIndex(crate::queue::Error),
}
pub struct VirtioInterruptIntx {
interrupt_status: Arc<AtomicUsize>,
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
@@ -58,6 +69,18 @@ impl VirtioInterrupt for VirtioInterruptIntx {
}
}
#[derive(Serialize, Deserialize)]
struct VirtioMmioDeviceState {
device_activated: bool,
features_select: u32,
acked_features_select: u32,
queue_select: u32,
interrupt_status: usize,
driver_status: u32,
queues: Vec<Queue>,
shm_region_select: u32,
}
/// Implements the
/// [MMIO](http://docs.oasis-open.org/virtio/virtio/v1.0/cs04/virtio-v1.0-cs04.html#x1-1090002)
/// transport for virtio devices.
@@ -73,6 +96,7 @@ impl VirtioInterrupt for VirtioInterruptIntx {
/// Typically one page (4096 bytes) of MMIO address space is sufficient to handle this transport
/// and inner virtio device.
pub struct MmioDevice {
id: String,
device: Arc<Mutex<dyn VirtioDevice>>,
device_activated: bool,
@@ -92,6 +116,7 @@ pub struct MmioDevice {
impl MmioDevice {
/// Constructs a new MMIO transport for the given virtio device.
pub fn new(
id: String,
mem: GuestMemoryAtomic<GuestMemoryMmap>,
device: Arc<Mutex<dyn VirtioDevice>>,
) -> Result<MmioDevice> {
@@ -107,6 +132,7 @@ impl MmioDevice {
.map(|&s| Queue::new(s))
.collect();
Ok(MmioDevice {
id,
device,
device_activated: false,
features_select: 0,
@@ -123,6 +149,51 @@ impl MmioDevice {
})
}
fn state(&self) -> VirtioMmioDeviceState {
VirtioMmioDeviceState {
device_activated: self.device_activated,
features_select: self.features_select,
acked_features_select: self.acked_features_select,
queue_select: self.queue_select,
interrupt_status: self.interrupt_status.load(Ordering::SeqCst),
driver_status: self.driver_status,
queues: self.queues.clone(),
shm_region_select: self.shm_region_select,
}
}
fn set_state(&mut self, state: &VirtioMmioDeviceState) -> std::result::Result<(), Error> {
self.device_activated = state.device_activated;
self.features_select = state.features_select;
self.acked_features_select = state.acked_features_select;
self.queue_select = state.queue_select;
self.interrupt_status
.store(state.interrupt_status, Ordering::SeqCst);
self.driver_status = state.driver_status;
self.queues = state.queues.clone();
// Update virtqueues indexes for both available and used rings.
if let Some(mem) = self.mem.as_ref() {
let mem = mem.memory();
for queue in self.queues.iter_mut() {
queue.next_avail = Wrapping(
queue
.used_index_from_memory(&mem)
.map_err(Error::QueueRingIndex)?,
);
queue.next_used = Wrapping(
queue
.used_index_from_memory(&mem)
.map_err(Error::QueueRingIndex)?,
);
}
}
self.shm_region_select = state.shm_region_select;
Ok(())
}
/// Gets the list of queue events that must be triggered whenever the VM writes to
/// `virtio::NOTIFY_REG_OFFSET` past the MMIO base. Each event must be triggered when the
/// value being written equals the index of the event in this list.
@@ -354,5 +425,81 @@ impl Pausable for MmioDevice {
}
}
impl Snapshotable for MmioDevice {}
impl Snapshottable for MmioDevice {
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 virtio_mmio_dev_snapshot = Snapshot::new(self.id.as_str());
virtio_mmio_dev_snapshot.add_data_section(SnapshotDataSection {
id: format!("{}-section", self.id),
snapshot,
});
Ok(virtio_mmio_dev_snapshot)
}
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
if let Some(virtio_mmio_dev_section) =
snapshot.snapshot_data.get(&format!("{}-section", self.id))
{
let virtio_mmio_dev_state =
match serde_json::from_slice(&virtio_mmio_dev_section.snapshot) {
Ok(state) => state,
Err(error) => {
return Err(MigratableError::Restore(anyhow!(
"Could not deserialize VIRTIO_MMIO_DEVICE {}",
error
)))
}
};
// First restore the status of the virtqueues.
self.set_state(&virtio_mmio_dev_state).map_err(|e| {
MigratableError::Restore(anyhow!(
"Could not restore VIRTIO_MMIO_DEVICE state {:?}",
e
))
})?;
// Then we can activate the device, as we know at this point that
// the virtqueues are in the right state and the device is ready
// to be activated, which will spawn each virtio worker thread.
if self.device_activated && self.is_driver_ready() && self.are_queues_valid() {
if let Some(interrupt_cb) = self.interrupt_cb.take() {
if self.mem.is_some() {
let mem = self.mem.as_ref().unwrap().clone();
self.device
.lock()
.unwrap()
.activate(
mem,
interrupt_cb,
self.queues.clone(),
self.queue_evts.split_off(0),
)
.map_err(|e| {
MigratableError::Restore(anyhow!(
"Failed activating the device: {:?}",
e
))
})?;
}
}
}
return Ok(());
}
Err(MigratableError::Restore(anyhow!(
"Could not find VIRTIO_MMIO_DEVICE snapshot section"
)))
}
}
impl Transportable for MmioDevice {}
impl Migratable for MmioDevice {}

View File

@@ -37,11 +37,11 @@ use vm_allocator::SystemAllocator;
use vm_device::interrupt::{
InterruptIndex, InterruptManager, InterruptSourceGroup, MsiIrqGroupConfig,
};
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
use vm_memory::{
Address, ByteValued, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap,
GuestUsize, Le32,
};
use vm_migration::{Migratable, MigratableError, Pausable, Snapshottable, Transportable};
use vmm_sys_util::{errno::Result, eventfd::EventFd};
#[allow(clippy::enum_variant_names)]
@@ -259,6 +259,8 @@ const VIRTIO_PCI_VENDOR_ID: u16 = 0x1af4;
const VIRTIO_PCI_DEVICE_ID_BASE: u16 = 0x1040; // Add to device type to get device ID.
pub struct VirtioPciDevice {
id: String,
// PCI configuration registers.
configuration: PciConfiguration,
@@ -300,11 +302,15 @@ pub struct VirtioPciDevice {
// needed when the guest tries to early access the virtio configuration of
// a device.
cap_pci_cfg_info: VirtioPciCfgCapInfo,
// Details of bar regions to free
bar_regions: Vec<(GuestAddress, GuestUsize, PciBarRegionType)>,
}
impl VirtioPciDevice {
/// Constructs a new PCI transport for the given virtio device.
pub fn new(
id: String,
memory: GuestMemoryAtomic<GuestMemoryMmap>,
device: Arc<Mutex<dyn VirtioDevice>>,
msix_num: u16,
@@ -370,6 +376,7 @@ impl VirtioPciDevice {
let configuration = PciConfiguration::new(
VIRTIO_PCI_VENDOR_ID,
pci_device_id,
0x1, // For modern virtio-PCI devices
class,
subclass,
None,
@@ -380,6 +387,7 @@ impl VirtioPciDevice {
);
let mut virtio_pci_device = VirtioPciDevice {
id,
configuration,
common_config: VirtioPciCommonConfig {
driver_status: 0,
@@ -402,6 +410,7 @@ impl VirtioPciDevice {
use_64bit_bar,
interrupt_source_group,
cap_pci_cfg_info: VirtioPciCfgCapInfo::default(),
bar_regions: vec![],
};
if let Some(msix_config) = &virtio_pci_device.msix_config {
@@ -560,6 +569,10 @@ impl VirtioPciDevice {
self.write_bar(0, bar_offset as u64, data)
}
}
pub fn virtio_device(&self) -> Arc<Mutex<dyn VirtioDevice>> {
self.device.clone()
}
}
impl VirtioTransport for VirtioPciDevice {
@@ -721,6 +734,8 @@ impl PciDevice for VirtioPciDevice {
ranges.push((addr, CAPABILITY_BAR_SIZE, region_type));
(addr, region_type)
};
self.bar_regions
.push((virtio_pci_bar_addr, CAPABILITY_BAR_SIZE, region_type));
let config = PciBarConfiguration::default()
.set_register_index(0)
@@ -746,6 +761,11 @@ impl PciDevice for VirtioPciDevice {
PciDeviceError::IoRegistrationFailed(shm_list.addr.raw_value(), e)
})? as u8;
let region_type = PciBarRegionType::Memory64BitRegion;
ranges.push((shm_list.addr, shm_list.len, region_type));
self.bar_regions
.push((shm_list.addr, shm_list.len, region_type));
for (idx, shm) in shm_list.region_list.iter().enumerate() {
let shm_cap = VirtioPciCap64::new(
PciCapabilityType::SharedMemoryConfig,
@@ -763,6 +783,36 @@ impl PciDevice for VirtioPciDevice {
Ok(ranges)
}
fn free_bars(
&mut self,
allocator: &mut SystemAllocator,
) -> std::result::Result<(), PciDeviceError> {
for (addr, length, type_) in self.bar_regions.drain(..) {
match type_ {
PciBarRegionType::Memory32BitRegion => {
allocator.free_mmio_hole_addresses(addr, length);
}
PciBarRegionType::Memory64BitRegion => {
allocator.free_mmio_addresses(addr, length);
}
_ => error!("Unexpected PCI bar type"),
}
}
Ok(())
}
fn move_bar(&mut self, old_base: u64, new_base: u64) -> result::Result<(), std::io::Error> {
// We only update our idea of the bar in order to support free_bars() above.
// The majority of the reallocation is done inside DeviceManager.
for (addr, _, _) in self.bar_regions.iter_mut() {
if (*addr).0 == old_base {
*addr = GuestAddress(new_base);
}
}
Ok(())
}
fn read_bar(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
match offset {
o if o < COMMON_CONFIG_BAR_OFFSET + COMMON_CONFIG_SIZE => self.common_config.read(
@@ -916,5 +966,10 @@ impl Pausable for VirtioPciDevice {
}
}
impl Snapshotable for VirtioPciDevice {}
impl Snapshottable for VirtioPciDevice {
fn id(&self) -> String {
self.id.clone()
}
}
impl Transportable for VirtioPciDevice {}
impl Migratable for VirtioPciDevice {}

View File

@@ -26,14 +26,15 @@ use vhost_rs::vhost_user::{Master, VhostUserMaster, VhostUserMasterReqHandler};
use vhost_rs::VhostBackend;
use virtio_bindings::bindings::virtio_blk::*;
use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
use vm_memory::{ByteValued, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
use vm_migration::{Migratable, MigratableError, Pausable, Snapshottable, Transportable};
use vmm_sys_util::eventfd::EventFd;
struct SlaveReqHandler {}
impl VhostUserMasterReqHandler for SlaveReqHandler {}
pub struct Blk {
id: String,
vhost_user_blk: Master,
kill_evt: Option<EventFd>,
pause_evt: Option<EventFd>,
@@ -49,7 +50,7 @@ pub struct Blk {
impl Blk {
/// Create a new vhost-user-blk device
pub fn new(wce: bool, vu_cfg: VhostUserConfig) -> Result<Blk> {
pub fn new(id: String, wce: bool, vu_cfg: VhostUserConfig) -> Result<Blk> {
let mut vhost_user_blk = Master::connect(&vu_cfg.sock, vu_cfg.num_queues as u64)
.map_err(Error::VhostUserCreateMaster)?;
@@ -140,6 +141,7 @@ impl Blk {
}
Ok(Blk {
id,
vhost_user_blk,
kill_evt: None,
pause_evt: None,
@@ -321,8 +323,17 @@ impl VirtioDevice for Blk {
fn shutdown(&mut self) {
let _ = unsafe { libc::close(self.vhost_user_blk.as_raw_fd()) };
}
fn update_memory(&mut self, mem: &GuestMemoryMmap) -> std::result::Result<(), crate::Error> {
update_mem_table(&mut self.vhost_user_blk, mem).map_err(crate::Error::VhostUserUpdateMemory)
}
}
virtio_pausable!(Blk);
impl Snapshotable for Blk {}
impl Snapshottable for Blk {
fn id(&self) -> String {
self.id.clone()
}
}
impl Transportable for Blk {}
impl Migratable for Blk {}

View File

@@ -1,19 +1,19 @@
// Copyright 2019 Intel Corporation. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use super::vu_common_ctrl::{reset_vhost_user, setup_vhost_user};
use super::vu_common_ctrl::{reset_vhost_user, setup_vhost_user, update_mem_table};
use super::Error as DeviceError;
use super::{Error, Result};
use crate::vhost_user::handler::{VhostUserEpollConfig, VhostUserEpollHandler};
use crate::{
ActivateError, ActivateResult, Queue, VirtioDevice, VirtioDeviceType, VirtioInterrupt,
VirtioSharedMemoryList, VIRTIO_F_VERSION_1,
ActivateError, ActivateResult, Queue, UserspaceMapping, VirtioDevice, VirtioDeviceType,
VirtioInterrupt, VirtioSharedMemoryList, VIRTIO_F_VERSION_1,
};
use libc::{self, c_void, off64_t, pread64, pwrite64, EFD_NONBLOCK};
use std::cmp;
use std::io;
use std::io::Write;
use std::os::unix::io::RawFd;
use std::os::unix::io::{AsRawFd, RawFd};
use std::result;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
@@ -26,10 +26,11 @@ use vhost_rs::vhost_user::{
HandlerResult, Master, MasterReqHandler, VhostUserMaster, VhostUserMasterReqHandler,
};
use vhost_rs::VhostBackend;
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
use vm_memory::{
Address, ByteValued, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap,
Address, ByteValued, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
GuestMemoryMmap, MmapRegion,
};
use vm_migration::{Migratable, MigratableError, Pausable, Snapshottable, Transportable};
use vmm_sys_util::eventfd::EventFd;
const NUM_QUEUE_OFFSET: usize = 1;
@@ -38,32 +39,48 @@ struct SlaveReqHandler {
cache_offset: GuestAddress,
cache_size: u64,
mmap_cache_addr: u64,
mem: GuestMemoryAtomic<GuestMemoryMmap>,
}
impl SlaveReqHandler {
// Make sure request is within cache range
fn is_req_valid(&self, offset: u64, len: u64) -> bool {
let end = match offset.checked_add(len) {
Some(n) => n,
None => return false,
};
!(offset >= self.cache_size || end > self.cache_size)
}
}
impl VhostUserMasterReqHandler for SlaveReqHandler {
fn handle_config_change(&mut self) -> HandlerResult<()> {
fn handle_config_change(&mut self) -> HandlerResult<u64> {
debug!("handle_config_change");
Ok(())
Ok(0)
}
fn fs_slave_map(&mut self, fs: &VhostUserFSSlaveMsg, fd: RawFd) -> HandlerResult<()> {
fn fs_slave_map(&mut self, fs: &VhostUserFSSlaveMsg, fd: RawFd) -> HandlerResult<u64> {
debug!("fs_slave_map");
for i in 0..VHOST_USER_FS_SLAVE_ENTRIES {
let offset = fs.cache_offset[i];
let len = fs.len[i];
// Ignore if the length is 0.
if fs.len[i] == 0 {
if len == 0 {
continue;
}
if fs.cache_offset[i] > self.cache_size {
return Err(io::Error::new(io::ErrorKind::Other, "Wrong offset"));
if !self.is_req_valid(offset, len) {
return Err(io::Error::from_raw_os_error(libc::EINVAL));
}
let addr = self.mmap_cache_addr + fs.cache_offset[i];
let addr = self.mmap_cache_addr + offset;
let ret = unsafe {
libc::mmap(
addr as *mut libc::c_void,
fs.len[i] as usize,
len as usize,
fs.flags[i].bits() as i32,
libc::MAP_SHARED | libc::MAP_FIXED,
fd,
@@ -80,13 +97,14 @@ impl VhostUserMasterReqHandler for SlaveReqHandler {
}
}
Ok(())
Ok(0)
}
fn fs_slave_unmap(&mut self, fs: &VhostUserFSSlaveMsg) -> HandlerResult<()> {
fn fs_slave_unmap(&mut self, fs: &VhostUserFSSlaveMsg) -> HandlerResult<u64> {
debug!("fs_slave_unmap");
for i in 0..VHOST_USER_FS_SLAVE_ENTRIES {
let offset = fs.cache_offset[i];
let mut len = fs.len[i];
// Ignore if the length is 0.
@@ -100,11 +118,11 @@ impl VhostUserMasterReqHandler for SlaveReqHandler {
len = self.cache_size;
}
if fs.cache_offset[i] > self.cache_size {
return Err(io::Error::new(io::ErrorKind::Other, "Wrong offset"));
if !self.is_req_valid(offset, len) {
return Err(io::Error::from_raw_os_error(libc::EINVAL));
}
let addr = self.mmap_cache_addr + fs.cache_offset[i];
let addr = self.mmap_cache_addr + offset;
let ret = unsafe {
libc::mmap(
addr as *mut libc::c_void,
@@ -120,37 +138,40 @@ impl VhostUserMasterReqHandler for SlaveReqHandler {
}
}
Ok(())
Ok(0)
}
fn fs_slave_sync(&mut self, fs: &VhostUserFSSlaveMsg) -> HandlerResult<()> {
fn fs_slave_sync(&mut self, fs: &VhostUserFSSlaveMsg) -> HandlerResult<u64> {
debug!("fs_slave_sync");
for i in 0..VHOST_USER_FS_SLAVE_ENTRIES {
let offset = fs.cache_offset[i];
let len = fs.len[i];
// Ignore if the length is 0.
if fs.len[i] == 0 {
if len == 0 {
continue;
}
if fs.cache_offset[i] > self.cache_size {
return Err(io::Error::new(io::ErrorKind::Other, "Wrong offset"));
if !self.is_req_valid(offset, len) {
return Err(io::Error::from_raw_os_error(libc::EINVAL));
}
let addr = self.mmap_cache_addr + fs.cache_offset[i];
let ret = unsafe {
libc::msync(addr as *mut libc::c_void, fs.len[i] as usize, libc::MS_SYNC)
};
let addr = self.mmap_cache_addr + offset;
let ret =
unsafe { libc::msync(addr as *mut libc::c_void, len as usize, libc::MS_SYNC) };
if ret == -1 {
return Err(io::Error::last_os_error());
}
}
Ok(())
Ok(0)
}
fn fs_slave_io(&mut self, fs: &VhostUserFSSlaveMsg, fd: RawFd) -> HandlerResult<()> {
fn fs_slave_io(&mut self, fs: &VhostUserFSSlaveMsg, fd: RawFd) -> HandlerResult<u64> {
debug!("fs_slave_io");
let mut done: u64 = 0;
for i in 0..VHOST_USER_FS_SLAVE_ENTRIES {
// Ignore if the length is 0.
if fs.len[i] == 0 {
@@ -160,19 +181,34 @@ impl VhostUserMasterReqHandler for SlaveReqHandler {
let mut foffset = fs.fd_offset[i];
let mut len = fs.len[i] as usize;
let gpa = fs.cache_offset[i];
if gpa < self.cache_offset.raw_value()
|| gpa >= self.cache_offset.raw_value() + self.cache_size
{
return Err(io::Error::new(
io::ErrorKind::Other,
"gpa is out of cache range",
));
}
let cache_end = self.cache_offset.raw_value() + self.cache_size;
let efault = libc::EFAULT;
let offset = gpa
.checked_sub(self.cache_offset.raw_value())
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "gpa is out of cache range"))?;
let mut ptr = self.mmap_cache_addr + offset;
let mut ptr = if gpa >= self.cache_offset.raw_value() && gpa < cache_end {
let offset = gpa
.checked_sub(self.cache_offset.raw_value())
.ok_or_else(|| io::Error::from_raw_os_error(efault))?;
let end = gpa
.checked_add(fs.len[i])
.ok_or_else(|| io::Error::from_raw_os_error(efault))?;
if end >= cache_end {
return Err(io::Error::from_raw_os_error(efault));
}
self.mmap_cache_addr + offset
} else {
self.mem
.memory()
.get_host_address(GuestAddress(gpa))
.map_err(|e| {
error!(
"Failed to find RAM region associated with guest physical address 0x{:x}: {:?}",
gpa, e
);
io::Error::from_raw_os_error(efault)
})? as u64
};
while len > 0 {
let ret = if (fs.flags[i] & VhostUserFSSlaveMsgFlags::MAP_W)
@@ -199,6 +235,7 @@ impl VhostUserMasterReqHandler for SlaveReqHandler {
len -= ret as usize;
foffset += ret as u64;
ptr += ret as u64;
done += ret as u64;
}
}
@@ -207,7 +244,7 @@ impl VhostUserMasterReqHandler for SlaveReqHandler {
return Err(io::Error::last_os_error());
}
Ok(())
Ok(done)
}
}
@@ -230,6 +267,7 @@ impl Default for VirtioFsConfig {
unsafe impl ByteValued for VirtioFsConfig {}
pub struct Fs {
id: String,
vu: Master,
queue_sizes: Vec<u16>,
avail_features: u64,
@@ -237,7 +275,9 @@ pub struct Fs {
config: VirtioFsConfig,
kill_evt: Option<EventFd>,
pause_evt: Option<EventFd>,
cache: Option<(VirtioSharedMemoryList, u64)>,
// Hold ownership of the memory that is allocated for the device
// which will be automatically dropped when the device is dropped
cache: Option<(VirtioSharedMemoryList, MmapRegion)>,
slave_req_support: bool,
queue_evts: Option<Vec<EventFd>>,
interrupt_cb: Option<Arc<dyn VirtioInterrupt>>,
@@ -248,11 +288,12 @@ pub struct Fs {
impl Fs {
/// Create a new virtio-fs device.
pub fn new(
id: String,
path: &str,
tag: &str,
req_num_queues: usize,
queue_size: u16,
cache: Option<(VirtioSharedMemoryList, u64)>,
cache: Option<(VirtioSharedMemoryList, MmapRegion)>,
) -> Result<Fs> {
let mut slave_req_support = false;
@@ -313,6 +354,7 @@ impl Fs {
config.num_request_queues = req_num_queues as u32;
Ok(Fs {
id,
vu: master,
queue_sizes: vec![queue_size; num_queues],
avail_features,
@@ -450,11 +492,12 @@ impl VirtioDevice for Fs {
// Initialize slave communication.
let slave_req_handler = if self.slave_req_support {
if let Some(cache) = self.cache.clone() {
if let Some(cache) = self.cache.as_ref() {
let vu_master_req_handler = Arc::new(Mutex::new(SlaveReqHandler {
cache_offset: cache.0.addr,
cache_size: cache.0.len,
mmap_cache_addr: cache.1,
mmap_cache_addr: cache.0.host_addr,
mem,
}));
let req_handler = MasterReqHandler::new(vu_master_req_handler).map_err(|e| {
@@ -520,15 +563,55 @@ impl VirtioDevice for Fs {
))
}
fn shutdown(&mut self) {
let _ = unsafe { libc::close(self.vu.as_raw_fd()) };
}
fn get_shm_regions(&self) -> Option<VirtioSharedMemoryList> {
if let Some(cache) = self.cache.clone() {
Some(cache.0)
if let Some(cache) = self.cache.as_ref() {
Some(cache.0.clone())
} else {
None
}
}
fn set_shm_regions(
&mut self,
shm_regions: VirtioSharedMemoryList,
) -> std::result::Result<(), crate::Error> {
if let Some(mut cache) = self.cache.as_mut() {
cache.0 = shm_regions;
Ok(())
} else {
Err(crate::Error::SetShmRegionsNotSupported)
}
}
fn update_memory(&mut self, mem: &GuestMemoryMmap) -> std::result::Result<(), crate::Error> {
update_mem_table(&mut self.vu, mem).map_err(crate::Error::VhostUserUpdateMemory)
}
fn userspace_mappings(&self) -> Vec<UserspaceMapping> {
let mut mappings = Vec::new();
if let Some(cache) = self.cache.as_ref() {
mappings.push(UserspaceMapping {
host_addr: cache.0.host_addr,
mem_slot: cache.0.mem_slot,
addr: cache.0.addr,
len: cache.0.len,
mergeable: false,
})
}
mappings
}
}
virtio_pausable!(Fs);
impl Snapshotable for Fs {}
impl Snapshottable for Fs {
fn id(&self) -> String {
self.id.clone()
}
}
impl Transportable for Fs {}
impl Migratable for Fs {}

View File

@@ -27,8 +27,8 @@ use vhost_rs::vhost_user::{Master, VhostUserMaster, VhostUserMasterReqHandler};
use vhost_rs::VhostBackend;
use virtio_bindings::bindings::virtio_net;
use virtio_bindings::bindings::virtio_ring;
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
use vm_memory::{ByteValued, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
use vm_migration::{Migratable, MigratableError, Pausable, Snapshottable, Transportable};
use vmm_sys_util::eventfd::EventFd;
const DEFAULT_QUEUE_NUMBER: usize = 2;
@@ -37,6 +37,7 @@ struct SlaveReqHandler {}
impl VhostUserMasterReqHandler for SlaveReqHandler {}
pub struct Net {
id: String,
vhost_user_net: Master,
kill_evt: Option<EventFd>,
pause_evt: Option<EventFd>,
@@ -55,7 +56,7 @@ pub struct Net {
impl Net {
/// Create a new vhost-user-net device
/// Create a new vhost-user-net device
pub fn new(mac_addr: MacAddr, vu_cfg: VhostUserConfig) -> Result<Net> {
pub fn new(id: String, mac_addr: MacAddr, vu_cfg: VhostUserConfig) -> Result<Net> {
let mut vhost_user_net = Master::connect(&vu_cfg.sock, vu_cfg.num_queues as u64)
.map_err(Error::VhostUserCreateMaster)?;
@@ -141,6 +142,7 @@ impl Net {
}
Ok(Net {
id,
vhost_user_net,
kill_evt: None,
pause_evt: None,
@@ -358,8 +360,17 @@ impl VirtioDevice for Net {
fn shutdown(&mut self) {
let _ = unsafe { libc::close(self.vhost_user_net.as_raw_fd()) };
}
fn update_memory(&mut self, mem: &GuestMemoryMmap) -> std::result::Result<(), crate::Error> {
update_mem_table(&mut self.vhost_user_net, mem).map_err(crate::Error::VhostUserUpdateMemory)
}
}
virtio_ctrl_q_pausable!(Net);
impl Snapshotable for Net {}
impl Snapshottable for Net {
fn id(&self) -> String {
self.id.clone()
}
}
impl Transportable for Net {}
impl Migratable for Net {}

View File

@@ -27,13 +27,7 @@ pub struct VhostUserConfig {
pub queue_size: u16,
}
pub fn setup_vhost_user_vring(
vu: &mut Master,
mem: &GuestMemoryMmap,
queues: Vec<Queue>,
queue_evts: Vec<EventFd>,
virtio_interrupt: &Arc<dyn VirtioInterrupt>,
) -> Result<Vec<(Option<EventFd>, Queue)>> {
pub fn update_mem_table(vu: &mut Master, mem: &GuestMemoryMmap) -> Result<()> {
let mut regions: Vec<VhostUserMemoryRegionInfo> = Vec::new();
mem.with_regions_mut(|_, region| {
let (mmap_handle, mmap_offset) = match region.file_offset() {
@@ -58,6 +52,19 @@ pub fn setup_vhost_user_vring(
vu.set_mem_table(regions.as_slice())
.map_err(Error::VhostUserSetMemTable)?;
Ok(())
}
pub fn setup_vhost_user_vring(
vu: &mut Master,
mem: &GuestMemoryMmap,
queues: Vec<Queue>,
queue_evts: Vec<EventFd>,
virtio_interrupt: &Arc<dyn VirtioInterrupt>,
) -> Result<Vec<(Option<EventFd>, Queue)>> {
// Let's first provide the memory table to the backend.
update_mem_table(vu, mem)?;
let mut vu_interrupt_list = Vec::new();
for (queue_index, queue) in queues.into_iter().enumerate() {

View File

@@ -44,8 +44,8 @@ use std::result;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock};
use std::thread;
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
use vm_memory::{GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
use vm_migration::{Migratable, MigratableError, Pausable, Snapshottable, Transportable};
use vmm_sys_util::eventfd::EventFd;
const QUEUE_SIZE: u16 = 256;
@@ -376,6 +376,7 @@ where
/// Virtio device exposing virtual socket to the guest.
pub struct Vsock<B: VsockBackend> {
id: String,
cid: u64,
backend: Arc<RwLock<B>>,
kill_evt: Option<EventFd>,
@@ -394,7 +395,7 @@ where
{
/// Create a new virtio-vsock device with the given VM CID and vsock
/// backend.
pub fn new(cid: u64, backend: B, iommu: bool) -> io::Result<Vsock<B>> {
pub fn new(id: String, cid: u64, backend: B, iommu: bool) -> io::Result<Vsock<B>> {
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1 | 1u64 << VIRTIO_F_IN_ORDER;
if iommu {
@@ -402,6 +403,7 @@ where
}
Ok(Vsock {
id,
cid,
backend: Arc::new(RwLock::new(backend)),
kill_evt: None,
@@ -574,7 +576,15 @@ where
virtio_pausable!(Vsock, T: 'static + VsockBackend + Sync);
impl<B> Snapshotable for Vsock<B> where B: VsockBackend + Sync + 'static {}
impl<B> Snapshottable for Vsock<B>
where
B: VsockBackend + Sync + 'static,
{
fn id(&self) -> String {
self.id.clone()
}
}
impl<B> Transportable for Vsock<B> where B: VsockBackend + Sync + 'static {}
impl<B> Migratable for Vsock<B> where B: VsockBackend + Sync + 'static {}
#[cfg(test)]

View File

@@ -265,7 +265,7 @@ mod tests {
cid: CID,
mem: GuestMemoryMmap::from_ranges(&[(GuestAddress(0), MEM_SIZE)]).unwrap(),
mem_size: MEM_SIZE,
device: Vsock::new(CID, TestBackend::new(), false).unwrap(),
device: Vsock::new(String::from("vsock"), CID, TestBackend::new(), false).unwrap(),
}
}

View File

@@ -10,6 +10,7 @@ acpi = ["acpi_tables","devices/acpi"]
pci_support = ["pci", "vfio", "vm-virtio/pci_support"]
mmio_support = ["vm-virtio/mmio_support"]
cmos = ["devices/cmos"]
fwdebug = ["devices/fwdebug"]
[dependencies]
arc-swap = ">=0.4.4"
@@ -19,24 +20,27 @@ anyhow = "1.0"
arch = { path = "../arch" }
devices = { path = "../devices" }
epoll = ">=4.0.1"
kvm-bindings = "0.2.0"
kvm-ioctls = "0.5.0"
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch", features = ["with-serde", "fam-wrappers"] }
kvm-ioctls = { git = "https://github.com/cloud-hypervisor/kvm-ioctls", branch = "ch" }
lazy_static = "1.4.0"
libc = "0.2.68"
libc = "0.2.69"
log = "0.4.8"
micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "master" }
net_util = { path = "../net_util" }
pci = {path = "../pci", optional = true}
qcow = { path = "../qcow" }
seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v0.21.1" }
serde = {version = ">=1.0.27", features = ["rc"] }
serde_derive = ">=1.0.27"
serde_json = ">=1.0.9"
url = "2.1.1"
vfio = { path = "../vfio", optional = true }
vm-allocator = { path = "../vm-allocator" }
vm-device = { path = "../vm-device" }
vm-memory = { version = "0.1.0", features = ["backend-mmap", "backend-atomic"] }
vm-memory = { version = "0.2.0", features = ["backend-mmap", "backend-atomic"] }
vm-migration = { path = "../vm-migration" }
vm-virtio = { path = "../vm-virtio" }
vmm-sys-util = ">=0.3.1"
vmm-sys-util = { version = ">=0.5.0", features = ["with-serde"] }
signal-hook = "0.1.13"
tempfile = "3.1.0"

View File

@@ -4,11 +4,14 @@
//
use crate::api::http_endpoint::{
VmActionHandler, VmAddDevice, VmCreate, VmInfo, VmRemoveDevice, VmResize, VmmPing, VmmShutdown,
VmActionHandler, VmAddDevice, VmAddDisk, VmAddFs, VmAddNet, VmAddPmem, VmAddVsock, VmCreate,
VmInfo, VmRemoveDevice, VmResize, VmRestore, VmSnapshot, VmmPing, VmmShutdown,
};
use crate::api::{ApiRequest, VmAction};
use crate::seccomp_filters::{get_seccomp_filter, Thread};
use crate::{Error, Result};
use micro_http::{HttpServer, MediaType, Request, Response, StatusCode, Version};
use seccomp::{SeccompFilter, SeccompLevel};
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::mpsc::Sender;
@@ -59,11 +62,18 @@ lazy_static! {
r.routes.insert(endpoint!("/vm.resume"), Box::new(VmActionHandler::new(VmAction::Resume)));
r.routes.insert(endpoint!("/vm.shutdown"), Box::new(VmActionHandler::new(VmAction::Shutdown)));
r.routes.insert(endpoint!("/vm.reboot"), Box::new(VmActionHandler::new(VmAction::Reboot)));
r.routes.insert(endpoint!("/vm.snapshot"), Box::new(VmSnapshot {}));
r.routes.insert(endpoint!("/vm.restore"), Box::new(VmRestore {}));
r.routes.insert(endpoint!("/vmm.shutdown"), Box::new(VmmShutdown {}));
r.routes.insert(endpoint!("/vmm.ping"), Box::new(VmmPing {}));
r.routes.insert(endpoint!("/vm.resize"), Box::new(VmResize {}));
r.routes.insert(endpoint!("/vm.add-device"), Box::new(VmAddDevice {}));
r.routes.insert(endpoint!("/vm.remove-device"), Box::new(VmRemoveDevice {}));
r.routes.insert(endpoint!("/vm.add-disk"), Box::new(VmAddDisk {}));
r.routes.insert(endpoint!("/vm.add-fs"), Box::new(VmAddFs {}));
r.routes.insert(endpoint!("/vm.add-pmem"), Box::new(VmAddPmem {}));
r.routes.insert(endpoint!("/vm.add-net"), Box::new(VmAddNet {}));
r.routes.insert(endpoint!("/vm.add-vsock"), Box::new(VmAddVsock {}));
r
};
@@ -92,13 +102,21 @@ pub fn start_http_thread(
path: &str,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
seccomp_level: &SeccompLevel,
) -> Result<thread::JoinHandle<Result<()>>> {
std::fs::remove_file(path).unwrap_or_default();
let socket_path = PathBuf::from(path);
// Retrieve seccomp filter for API thread
let api_seccomp_filter =
get_seccomp_filter(seccomp_level, Thread::Api).map_err(Error::CreateSeccompFilter)?;
thread::Builder::new()
.name("http-server".to_string())
.spawn(move || {
// Apply seccomp filter for API thread.
SeccompFilter::apply(api_seccomp_filter).map_err(Error::ApplySeccompFilter)?;
let mut server = HttpServer::new(socket_path).unwrap();
server.start_server().unwrap();
loop {

View File

@@ -5,9 +5,11 @@
use crate::api::http::EndpointHandler;
use crate::api::{
vm_add_device, vm_boot, vm_create, vm_delete, vm_info, vm_pause, vm_reboot, vm_remove_device,
vm_resize, vm_resume, vm_shutdown, vmm_ping, vmm_shutdown, ApiError, ApiRequest, ApiResult,
DeviceConfig, VmAction, VmConfig, VmRemoveDeviceData, VmResizeData,
vm_add_device, vm_add_disk, vm_add_fs, vm_add_net, vm_add_pmem, vm_add_vsock, vm_boot,
vm_create, vm_delete, vm_info, vm_pause, vm_reboot, vm_remove_device, vm_resize, vm_restore,
vm_resume, vm_shutdown, vm_snapshot, vmm_ping, vmm_shutdown, ApiError, ApiRequest, ApiResult,
DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, RestoreConfig, VmAction, VmConfig,
VmRemoveDeviceData, VmResizeData, VmSnapshotConfig, VsockConfig,
};
use micro_http::{Body, Method, Request, Response, StatusCode, Version};
use serde_json::Error as SerdeError;
@@ -42,6 +44,12 @@ pub enum HttpError {
/// Could not reboot a VM
VmReboot(ApiError),
/// Could not snapshot a VM
VmSnapshot(ApiError),
/// Could not restore a VM
VmRestore(ApiError),
/// Could not act on a VM
VmAction(ApiError),
@@ -59,6 +67,21 @@ pub enum HttpError {
/// Could not handle VMM ping
VmmPing(ApiError),
/// Could not add a disk to a VM
VmAddDisk(ApiError),
/// Could not add a fs to a VM
VmAddFs(ApiError),
/// Could not add a pmem device to a VM
VmAddPmem(ApiError),
/// Could not add a network device to a VM
VmAddNet(ApiError),
/// Could not add a vsock device to a VM
VmAddVsock(ApiError),
}
fn error_response(error: HttpError, status: StatusCode) -> Response {
@@ -182,6 +205,84 @@ impl EndpointHandler for VmInfo {
}
}
// /api/v1/vm.snapshot handler
pub struct VmSnapshot {}
impl EndpointHandler for VmSnapshot {
fn handle_request(
&self,
req: &Request,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
) -> Response {
match req.method() {
Method::Put => {
match &req.body {
Some(body) => {
// Deserialize into a VmSnapshotConfig
let vm_snapshot_data: VmSnapshotConfig =
match serde_json::from_slice(body.raw())
.map_err(HttpError::SerdeJsonDeserialize)
{
Ok(data) => data,
Err(e) => return error_response(e, StatusCode::BadRequest),
};
// Call vm_snapshot()
match vm_snapshot(api_notifier, api_sender, Arc::new(vm_snapshot_data))
.map_err(HttpError::VmSnapshot)
{
Ok(_) => Response::new(Version::Http11, StatusCode::NoContent),
Err(e) => error_response(e, StatusCode::InternalServerError),
}
}
None => Response::new(Version::Http11, StatusCode::BadRequest),
}
}
_ => Response::new(Version::Http11, StatusCode::BadRequest),
}
}
}
// /api/v1/vm.restore handler
pub struct VmRestore {}
impl EndpointHandler for VmRestore {
fn handle_request(
&self,
req: &Request,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
) -> Response {
match req.method() {
Method::Put => {
match &req.body {
Some(body) => {
// Deserialize into a RestoreConfig
let vm_restore_data: RestoreConfig =
match serde_json::from_slice(body.raw())
.map_err(HttpError::SerdeJsonDeserialize)
{
Ok(data) => data,
Err(e) => return error_response(e, StatusCode::BadRequest),
};
// Call vm_restore()
match vm_restore(api_notifier, api_sender, Arc::new(vm_restore_data))
.map_err(HttpError::VmRestore)
{
Ok(_) => Response::new(Version::Http11, StatusCode::NoContent),
Err(e) => error_response(e, StatusCode::InternalServerError),
}
}
None => Response::new(Version::Http11, StatusCode::BadRequest),
}
}
_ => Response::new(Version::Http11, StatusCode::BadRequest),
}
}
}
// /api/v1/vmm.info handler
pub struct VmmPing {}
@@ -352,3 +453,196 @@ impl EndpointHandler for VmRemoveDevice {
}
}
}
// /api/v1/vm.add-disk handler
pub struct VmAddDisk {}
impl EndpointHandler for VmAddDisk {
fn handle_request(
&self,
req: &Request,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
) -> Response {
match req.method() {
Method::Put => {
match &req.body {
Some(body) => {
// Deserialize into a DiskConfig
let vm_add_disk_data: DiskConfig = match serde_json::from_slice(body.raw())
.map_err(HttpError::SerdeJsonDeserialize)
{
Ok(config) => config,
Err(e) => return error_response(e, StatusCode::BadRequest),
};
// Call vm_add_device()
match vm_add_disk(api_notifier, api_sender, Arc::new(vm_add_disk_data))
.map_err(HttpError::VmAddDisk)
{
Ok(_) => Response::new(Version::Http11, StatusCode::NoContent),
Err(e) => error_response(e, StatusCode::InternalServerError),
}
}
None => Response::new(Version::Http11, StatusCode::BadRequest),
}
}
_ => Response::new(Version::Http11, StatusCode::BadRequest),
}
}
}
// /api/v1/vm.add-fs handler
pub struct VmAddFs {}
impl EndpointHandler for VmAddFs {
fn handle_request(
&self,
req: &Request,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
) -> Response {
match req.method() {
Method::Put => {
match &req.body {
Some(body) => {
// Deserialize into a FsConfig
let vm_add_fs_data: FsConfig = match serde_json::from_slice(body.raw())
.map_err(HttpError::SerdeJsonDeserialize)
{
Ok(config) => config,
Err(e) => return error_response(e, StatusCode::BadRequest),
};
// Call vm_add_fs()
match vm_add_fs(api_notifier, api_sender, Arc::new(vm_add_fs_data))
.map_err(HttpError::VmAddFs)
{
Ok(_) => Response::new(Version::Http11, StatusCode::NoContent),
Err(e) => error_response(e, StatusCode::InternalServerError),
}
}
None => Response::new(Version::Http11, StatusCode::BadRequest),
}
}
_ => Response::new(Version::Http11, StatusCode::BadRequest),
}
}
}
// /api/v1/vm.add-pmem handler
pub struct VmAddPmem {}
impl EndpointHandler for VmAddPmem {
fn handle_request(
&self,
req: &Request,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
) -> Response {
match req.method() {
Method::Put => {
match &req.body {
Some(body) => {
// Deserialize into a PmemConfig
let vm_add_pmem_data: PmemConfig = match serde_json::from_slice(body.raw())
.map_err(HttpError::SerdeJsonDeserialize)
{
Ok(config) => config,
Err(e) => return error_response(e, StatusCode::BadRequest),
};
match vm_add_pmem(api_notifier, api_sender, Arc::new(vm_add_pmem_data))
.map_err(HttpError::VmAddPmem)
{
Ok(_) => Response::new(Version::Http11, StatusCode::NoContent),
Err(e) => error_response(e, StatusCode::InternalServerError),
}
}
None => Response::new(Version::Http11, StatusCode::BadRequest),
}
}
_ => Response::new(Version::Http11, StatusCode::BadRequest),
}
}
}
// /api/v1/vm.add-net handler
pub struct VmAddNet {}
impl EndpointHandler for VmAddNet {
fn handle_request(
&self,
req: &Request,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
) -> Response {
match req.method() {
Method::Put => {
match &req.body {
Some(body) => {
// Deserialize into a NetConfig
let vm_add_net_data: NetConfig = match serde_json::from_slice(body.raw())
.map_err(HttpError::SerdeJsonDeserialize)
{
Ok(config) => config,
Err(e) => return error_response(e, StatusCode::BadRequest),
};
match vm_add_net(api_notifier, api_sender, Arc::new(vm_add_net_data))
.map_err(HttpError::VmAddNet)
{
Ok(_) => Response::new(Version::Http11, StatusCode::NoContent),
Err(e) => error_response(e, StatusCode::InternalServerError),
}
}
None => Response::new(Version::Http11, StatusCode::BadRequest),
}
}
_ => Response::new(Version::Http11, StatusCode::BadRequest),
}
}
}
// /api/v1/vm.add-vsock handler
pub struct VmAddVsock {}
impl EndpointHandler for VmAddVsock {
fn handle_request(
&self,
req: &Request,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
) -> Response {
match req.method() {
Method::Put => {
match &req.body {
Some(body) => {
// Deserialize into a VsockConfig
let vm_add_vsock_data: VsockConfig =
match serde_json::from_slice(body.raw())
.map_err(HttpError::SerdeJsonDeserialize)
{
Ok(config) => config,
Err(e) => return error_response(e, StatusCode::BadRequest),
};
match vm_add_vsock(api_notifier, api_sender, Arc::new(vm_add_vsock_data))
.map_err(HttpError::VmAddVsock)
{
Ok(_) => Response::new(Version::Http11, StatusCode::NoContent),
Err(e) => error_response(e, StatusCode::InternalServerError),
}
}
None => Response::new(Version::Http11, StatusCode::BadRequest),
}
}
_ => Response::new(Version::Http11, StatusCode::BadRequest),
}
}
}

View File

@@ -29,6 +29,7 @@
//! 5. The thread handles the response and forwards potential errors.
extern crate micro_http;
extern crate vm_device;
extern crate vmm_sys_util;
pub use self::http::start_http_thread;
@@ -36,7 +37,9 @@ pub use self::http::start_http_thread;
pub mod http;
pub mod http_endpoint;
use crate::config::{DeviceConfig, VmConfig};
use crate::config::{
DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, RestoreConfig, VmConfig, VsockConfig,
};
use crate::vm::{Error as VmError, VmState};
use std::io;
use std::sync::mpsc::{channel, RecvError, SendError, Sender};
@@ -94,6 +97,12 @@ pub enum ApiError {
/// The VM could not reboot.
VmReboot(VmError),
/// The VM could not be snapshotted.
VmSnapshot(VmError),
/// The VM could not restored.
VmRestore(VmError),
/// The VMM could not shutdown.
VmmShutdown(VmError),
@@ -105,6 +114,27 @@ pub enum ApiError {
/// The device could not be removed from the VM.
VmRemoveDevice(VmError),
/// Cannot create seccomp filter
CreateSeccompFilter(seccomp::SeccompError),
/// Cannot apply seccomp filter
ApplySeccompFilter(seccomp::Error),
/// The disk could not be added to the VM.
VmAddDisk(VmError),
/// The fs could not be added to the VM.
VmAddFs(VmError),
/// The pmem device could not be added to the VM.
VmAddPmem(VmError),
/// The network device could not be added to the VM.
VmAddNet(VmError),
/// The vsock device could not be added to the VM.
VmAddVsock(VmError),
}
pub type ApiResult<T> = std::result::Result<T, ApiError>;
@@ -130,6 +160,12 @@ pub struct VmRemoveDeviceData {
pub id: String,
}
#[derive(Clone, Deserialize, Serialize)]
pub struct VmSnapshotConfig {
/// The snapshot destination URL
pub destination_url: String,
}
pub enum ApiResponsePayload {
/// No data is sent on the channel.
Empty,
@@ -198,6 +234,27 @@ pub enum ApiRequest {
/// Remove a device from the VM.
VmRemoveDevice(Arc<VmRemoveDeviceData>, Sender<ApiResponse>),
/// Add a disk to the VM.
VmAddDisk(Arc<DiskConfig>, Sender<ApiResponse>),
/// Add a fs to the VM.
VmAddFs(Arc<FsConfig>, Sender<ApiResponse>),
/// Add a pmem device to the VM.
VmAddPmem(Arc<PmemConfig>, Sender<ApiResponse>),
/// Add a network device to the VM.
VmAddNet(Arc<NetConfig>, Sender<ApiResponse>),
/// Add a vsock device to the VM.
VmAddVsock(Arc<VsockConfig>, Sender<ApiResponse>),
/// Take a VM snapshot
VmSnapshot(Arc<VmSnapshotConfig>, Sender<ApiResponse>),
/// Restore from a VM snapshot
VmRestore(Arc<RestoreConfig>, Sender<ApiResponse>),
}
pub fn vm_create(
@@ -286,6 +343,42 @@ pub fn vm_resume(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<
vm_action(api_evt, api_sender, VmAction::Resume)
}
pub fn vm_snapshot(
api_evt: EventFd,
api_sender: Sender<ApiRequest>,
data: Arc<VmSnapshotConfig>,
) -> ApiResult<()> {
let (response_sender, response_receiver) = channel();
// Send the VM snapshot request.
api_sender
.send(ApiRequest::VmSnapshot(data, response_sender))
.map_err(ApiError::RequestSend)?;
api_evt.write(1).map_err(ApiError::EventFdWrite)?;
response_receiver.recv().map_err(ApiError::ResponseRecv)??;
Ok(())
}
pub fn vm_restore(
api_evt: EventFd,
api_sender: Sender<ApiRequest>,
data: Arc<RestoreConfig>,
) -> ApiResult<()> {
let (response_sender, response_receiver) = channel();
// Send the VM restore request.
api_sender
.send(ApiRequest::VmRestore(data, response_sender))
.map_err(ApiError::RequestSend)?;
api_evt.write(1).map_err(ApiError::EventFdWrite)?;
response_receiver.recv().map_err(ApiError::ResponseRecv)??;
Ok(())
}
pub fn vm_info(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<VmInfo> {
let (response_sender, response_receiver) = channel();
@@ -386,3 +479,93 @@ pub fn vm_remove_device(
Ok(())
}
pub fn vm_add_disk(
api_evt: EventFd,
api_sender: Sender<ApiRequest>,
data: Arc<DiskConfig>,
) -> ApiResult<()> {
let (response_sender, response_receiver) = channel();
// Send the VM add-disk request.
api_sender
.send(ApiRequest::VmAddDisk(data, response_sender))
.map_err(ApiError::RequestSend)?;
api_evt.write(1).map_err(ApiError::EventFdWrite)?;
response_receiver.recv().map_err(ApiError::ResponseRecv)??;
Ok(())
}
pub fn vm_add_fs(
api_evt: EventFd,
api_sender: Sender<ApiRequest>,
data: Arc<FsConfig>,
) -> ApiResult<()> {
let (response_sender, response_receiver) = channel();
// Send the VM add-fs request.
api_sender
.send(ApiRequest::VmAddFs(data, response_sender))
.map_err(ApiError::RequestSend)?;
api_evt.write(1).map_err(ApiError::EventFdWrite)?;
response_receiver.recv().map_err(ApiError::ResponseRecv)??;
Ok(())
}
pub fn vm_add_pmem(
api_evt: EventFd,
api_sender: Sender<ApiRequest>,
data: Arc<PmemConfig>,
) -> ApiResult<()> {
let (response_sender, response_receiver) = channel();
// Send the VM add-pmem request.
api_sender
.send(ApiRequest::VmAddPmem(data, response_sender))
.map_err(ApiError::RequestSend)?;
api_evt.write(1).map_err(ApiError::EventFdWrite)?;
response_receiver.recv().map_err(ApiError::ResponseRecv)??;
Ok(())
}
pub fn vm_add_net(
api_evt: EventFd,
api_sender: Sender<ApiRequest>,
data: Arc<NetConfig>,
) -> ApiResult<()> {
let (response_sender, response_receiver) = channel();
// Send the VM add-net request.
api_sender
.send(ApiRequest::VmAddNet(data, response_sender))
.map_err(ApiError::RequestSend)?;
api_evt.write(1).map_err(ApiError::EventFdWrite)?;
response_receiver.recv().map_err(ApiError::ResponseRecv)??;
Ok(())
}
pub fn vm_add_vsock(
api_evt: EventFd,
api_sender: Sender<ApiRequest>,
data: Arc<VsockConfig>,
) -> ApiResult<()> {
let (response_sender, response_receiver) = channel();
// Send the VM add-vsock request.
api_sender
.send(ApiRequest::VmAddVsock(data, response_sender))
.map_err(ApiError::RequestSend)?;
api_evt.write(1).map_err(ApiError::EventFdWrite)?;
response_receiver.recv().map_err(ApiError::ResponseRecv)??;
Ok(())
}

View File

@@ -171,6 +171,121 @@ paths:
404:
description: The device could not be removed from the VM instance.
/vm.add-disk:
put:
summary: Add a new disk to the VM
requestBody:
description: The details of the new disk
content:
application/json:
schema:
$ref: '#/components/schemas/DiskConfig'
required: true
responses:
204:
description: The new disk was successfully added to the VM instance.
500:
description: The new disk could not be added to the VM instance.
/vm.add-fs:
put:
summary: Add a new virtio-fs device to the VM
requestBody:
description: The details of the new virtio-fs
content:
application/json:
schema:
$ref: '#/components/schemas/FsConfig'
required: true
responses:
204:
description: The new device was successfully added to the VM instance.
500:
description: The new device could not be added to the VM instance.
/vm.add-pmem:
put:
summary: Add a new pmem device to the VM
requestBody:
description: The details of the new pmem device
content:
application/json:
schema:
$ref: '#/components/schemas/PmemConfig'
required: true
responses:
204:
description: The new device was successfully added to the VM instance.
500:
description: The new device could not be added to the VM instance.
/vm.add-net:
put:
summary: Add a new network device to the VM
requestBody:
description: The details of the new network device
content:
application/json:
schema:
$ref: '#/components/schemas/NetConfig'
required: true
responses:
204:
description: The new device was successfully added to the VM instance.
500:
description: The new device could not be added to the VM instance.
/vm.add-vsock:
put:
summary: Add a new vsock device to the VM
requestBody:
description: The details of the new vsock device
content:
application/json:
schema:
$ref: '#/components/schemas/VsockConfig'
required: true
responses:
204:
description: The new device was successfully added to the VM instance.
500:
description: The new device could not be added to the VM instance.
/vm.snapshot:
put:
summary: Returns a VM snapshot.
requestBody:
description: The snapshot configuration
content:
application/json:
schema:
$ref: '#/components/schemas/VmSnapshotConfig'
required: true
responses:
204:
description: The VM instance was successfully snapshotted.
404:
description: The VM instance could not be snapshotted because it is not created.
405:
description: The VM instance could not be snapshotted because it is not booted.
/vm.restore:
put:
summary: Restore a VM from a snapshot.
requestBody:
description: The restore configuration
content:
application/json:
schema:
$ref: '#/components/schemas/RestoreConfig'
required: true
responses:
204:
description: The VM instance was successfully restored.
404:
description: The VM instance could not be restored because it is already created.
components:
schemas:
@@ -208,6 +323,8 @@ components:
$ref: '#/components/schemas/MemoryConfig'
kernel:
$ref: '#/components/schemas/KernelConfig'
initramfs:
$ref: '#/components/schemas/InitramfsConfig'
cmdline:
$ref: '#/components/schemas/CmdLineConfig'
disks:
@@ -237,8 +354,6 @@ components:
items:
$ref: '#/components/schemas/DeviceConfig'
vsock:
type: array
items:
$ref: '#/components/schemas/VsockConfig'
iommu:
type: boolean
@@ -277,6 +392,15 @@ components:
mergeable:
type: boolean
default: false
hotplug_method:
type: string
default: "acpi"
shared:
type: boolean
default: false
hugepages:
type: boolean
default: false
KernelConfig:
required:
@@ -286,6 +410,15 @@ components:
path:
type: string
InitramfsConfig:
nullable: true
required:
- path
type: object
properties:
path:
type: string
CmdLineConfig:
required:
- args
@@ -327,6 +460,8 @@ components:
poll_queue:
type: boolean
default: true
id:
type: string
NetConfig:
type: object
@@ -356,6 +491,8 @@ components:
default: false
vhost_socket:
type: string
id:
type: string
RngConfig:
required:
@@ -392,11 +529,12 @@ components:
type: integer
format: int64
default: 8589934592
id:
type: string
PmemConfig:
required:
- file
- size
type: object
properties:
file:
@@ -410,6 +548,11 @@ components:
mergeable:
type: boolean
default: false
discard_writes:
type: boolean
default: false
id:
type: string
ConsoleConfig:
required:
@@ -455,6 +598,8 @@ components:
iommu:
type: boolean
default: false
id:
type: string
VmResize:
type: object
@@ -478,3 +623,19 @@ components:
properties:
id:
type: string
VmSnapshotConfig:
type: object
properties:
destination_url:
type: string
RestoreConfig:
required:
- source_url
type: object
properties:
source_url:
type: string
prefault:
type: boolean

File diff suppressed because it is too large Load Diff

View File

@@ -10,27 +10,42 @@
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
//
use crate::config::CpusConfig;
use crate::device_manager::DeviceManager;
use crate::CPU_MANAGER_SNAPSHOT_ID;
#[cfg(feature = "acpi")]
use acpi_tables::{aml, aml::Aml, sdt::SDT};
use anyhow::anyhow;
#[cfg(feature = "acpi")]
use arch::layout;
use arch::EntryPoint;
use devices::{ioapic, BusDevice};
use kvm_bindings::CpuId;
use kvm_bindings::{
kvm_fpu, kvm_lapic_state, kvm_mp_state, kvm_regs, kvm_sregs, kvm_vcpu_events, kvm_xcrs,
kvm_xsave, CpuId, Msrs,
};
use kvm_ioctls::*;
use libc::{c_void, siginfo_t};
use serde_derive::{Deserialize, Serialize};
use std::cmp;
use std::os::unix::thread::JoinHandleExt;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Barrier, Mutex};
use std::thread;
use std::{fmt, io, result};
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
use vm_memory::{Address, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
Transportable,
};
use vmm_sys_util::eventfd::EventFd;
use vmm_sys_util::signal::{register_signal_handler, SIGRTMIN};
// CPUID feature bits
const TSC_DEADLINE_TIMER_ECX_BIT: u8 = 24; // tsc deadline timer ecx bit.
const HYPERVISOR_ECX_BIT: u8 = 31; // Hypervisor ecx bit.
// Debug I/O port
#[cfg(target_arch = "x86_64")]
const DEBUG_IOPORT: u16 = 0x80;
@@ -84,6 +99,9 @@ pub enum Error {
/// Cannot spawn a new vCPU thread.
VcpuSpawn(io::Error),
/// Cannot patch the CPU ID
PatchCpuId(kvm_ioctls::Error),
#[cfg(target_arch = "x86_64")]
/// Error configuring the general purpose registers
REGSConfiguration(arch::x86_64::regs::Error),
@@ -121,6 +139,60 @@ pub enum Error {
/// Asking for more vCPUs that we can have
DesiredVCPUCountExceedsMax,
/// Failed to get KVM vcpu lapic.
VcpuGetLapic(kvm_ioctls::Error),
/// Failed to set KVM vcpu lapic.
VcpuSetLapic(kvm_ioctls::Error),
/// Failed to get KVM vcpu MP state.
VcpuGetMpState(kvm_ioctls::Error),
/// Failed to set KVM vcpu MP state.
VcpuSetMpState(kvm_ioctls::Error),
/// Failed to get KVM vcpu msrs.
VcpuGetMsrs(kvm_ioctls::Error),
/// Failed to set KVM vcpu msrs.
VcpuSetMsrs(kvm_ioctls::Error),
/// Failed to get KVM vcpu regs.
VcpuGetRegs(kvm_ioctls::Error),
/// Failed to set KVM vcpu regs.
VcpuSetRegs(kvm_ioctls::Error),
/// Failed to get KVM vcpu sregs.
VcpuGetSregs(kvm_ioctls::Error),
/// Failed to set KVM vcpu sregs.
VcpuSetSregs(kvm_ioctls::Error),
/// Failed to get KVM vcpu events.
VcpuGetVcpuEvents(kvm_ioctls::Error),
/// Failed to set KVM vcpu events.
VcpuSetVcpuEvents(kvm_ioctls::Error),
/// Failed to get KVM vcpu FPU.
VcpuGetFpu(kvm_ioctls::Error),
/// Failed to set KVM vcpu FPU.
VcpuSetFpu(kvm_ioctls::Error),
/// Failed to get KVM vcpu XSAVE.
VcpuGetXsave(kvm_ioctls::Error),
/// Failed to set KVM vcpu XSAVE.
VcpuSetXsave(kvm_ioctls::Error),
/// Failed to get KVM vcpu XCRS.
VcpuGetXcrs(kvm_ioctls::Error),
/// Failed to set KVM vcpu XCRS.
VcpuSetXcrs(kvm_ioctls::Error),
}
pub type Result<T> = result::Result<T, Error>;
@@ -242,6 +314,19 @@ pub struct Vcpu {
vm_ts: std::time::Instant,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct VcpuKvmState {
msrs: Msrs,
vcpu_events: kvm_vcpu_events,
regs: kvm_regs,
sregs: kvm_sregs,
fpu: kvm_fpu,
lapic_state: kvm_lapic_state,
xsave: kvm_xsave,
xcrs: kvm_xcrs,
mp_state: kvm_mp_state,
}
impl Vcpu {
/// Constructs a new VCPU for `vm`.
///
@@ -256,17 +341,17 @@ impl Vcpu {
mmio_bus: Arc<devices::Bus>,
ioapic: Option<Arc<Mutex<ioapic::Ioapic>>>,
creation_ts: std::time::Instant,
) -> Result<Self> {
) -> Result<Arc<Mutex<Self>>> {
let kvm_vcpu = fd.create_vcpu(id).map_err(Error::VcpuFd)?;
// Initially the cpuid per vCPU is the one supported by this VM.
Ok(Vcpu {
Ok(Arc::new(Mutex::new(Vcpu {
fd: kvm_vcpu,
id,
io_bus,
mmio_bus,
ioapic,
vm_ts: creation_ts,
})
})))
}
/// Configures a x86_64 specific vcpu and should be called once per vcpu from the vcpu's thread.
@@ -375,6 +460,113 @@ impl Vcpu {
ts.as_micros()
);
}
fn kvm_state(&self) -> Result<VcpuKvmState> {
let mut msrs = arch::x86_64::regs::boot_msr_entries();
self.fd.get_msrs(&mut msrs).map_err(Error::VcpuGetMsrs)?;
let vcpu_events = self
.fd
.get_vcpu_events()
.map_err(Error::VcpuGetVcpuEvents)?;
let regs = self.fd.get_regs().map_err(Error::VcpuGetRegs)?;
let sregs = self.fd.get_sregs().map_err(Error::VcpuGetSregs)?;
let lapic_state = self.fd.get_lapic().map_err(Error::VcpuGetLapic)?;
let fpu = self.fd.get_fpu().map_err(Error::VcpuGetFpu)?;
let xsave = self.fd.get_xsave().map_err(Error::VcpuGetXsave)?;
let xcrs = self.fd.get_xcrs().map_err(Error::VcpuGetXsave)?;
let mp_state = self.fd.get_mp_state().map_err(Error::VcpuGetMpState)?;
Ok(VcpuKvmState {
msrs,
vcpu_events,
regs,
sregs,
fpu,
lapic_state,
xsave,
xcrs,
mp_state,
})
}
fn set_kvm_state(&mut self, state: &VcpuKvmState) -> Result<()> {
self.fd.set_regs(&state.regs).map_err(Error::VcpuSetRegs)?;
self.fd.set_fpu(&state.fpu).map_err(Error::VcpuSetFpu)?;
self.fd
.set_xsave(&state.xsave)
.map_err(Error::VcpuSetXsave)?;
self.fd
.set_sregs(&state.sregs)
.map_err(Error::VcpuSetSregs)?;
self.fd.set_xcrs(&state.xcrs).map_err(Error::VcpuSetXcrs)?;
self.fd.set_msrs(&state.msrs).map_err(Error::VcpuSetMsrs)?;
self.fd
.set_lapic(&state.lapic_state)
.map_err(Error::VcpuSetLapic)?;
self.fd
.set_mp_state(state.mp_state)
.map_err(Error::VcpuSetMpState)?;
Ok(())
}
}
const VCPU_SNAPSHOT_ID: &str = "vcpu";
impl Pausable for Vcpu {}
impl Snapshottable for Vcpu {
fn id(&self) -> String {
VCPU_SNAPSHOT_ID.to_string()
}
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
let snapshot = serde_json::to_vec(&self.kvm_state().map_err(|e| {
MigratableError::Snapshot(anyhow!("Could not get vCPU KVM state {:?}", e))
})?)
.map_err(|e| MigratableError::Snapshot(e.into()))?;
let mut vcpu_snapshot = Snapshot::new(&format!("{}", self.id));
vcpu_snapshot.add_data_section(SnapshotDataSection {
id: format!("{}-section", VCPU_SNAPSHOT_ID),
snapshot,
});
Ok(vcpu_snapshot)
}
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
if let Some(vcpu_section) = snapshot
.snapshot_data
.get(&format!("{}-section", VCPU_SNAPSHOT_ID))
{
let vcpu_state = match serde_json::from_slice(&vcpu_section.snapshot) {
Ok(state) => state,
Err(error) => {
return Err(MigratableError::Restore(anyhow!(
"Could not deserialize the vCPU snapshot {}",
error
)))
}
};
self.set_kvm_state(&vcpu_state).map_err(|e| {
MigratableError::Restore(anyhow!("Could not set the vCPU KVM state {:?}", e))
})?;
Ok(())
} else {
Err(MigratableError::Restore(anyhow!(
"Could not find the vCPU snapshot section"
)))
}
}
}
pub struct CpuManager {
@@ -391,6 +583,7 @@ pub struct CpuManager {
reset_evt: EventFd,
vcpu_states: Vec<VcpuState>,
selected_cpu: u8,
vcpus: Vec<Arc<Mutex<Vcpu>>>,
}
const CPU_ENABLE_FLAG: usize = 0;
@@ -478,7 +671,7 @@ impl VcpuState {
fn signal_thread(&self) {
if let Some(handle) = self.handle.as_ref() {
unsafe {
libc::pthread_kill(handle.as_pthread_t(), SIGRTMIN());
libc::pthread_kill(handle.as_pthread_t() as _, SIGRTMIN());
}
}
}
@@ -500,21 +693,21 @@ impl VcpuState {
impl CpuManager {
pub fn new(
boot_vcpus: u8,
max_vcpus: u8,
config: &CpusConfig,
device_manager: &Arc<Mutex<DeviceManager>>,
guest_memory: GuestMemoryAtomic<GuestMemoryMmap>,
kvm: &Kvm,
fd: Arc<VmFd>,
cpuid: CpuId,
reset_evt: EventFd,
) -> Result<Arc<Mutex<CpuManager>>> {
let mut vcpu_states = Vec::with_capacity(usize::from(max_vcpus));
vcpu_states.resize_with(usize::from(max_vcpus), VcpuState::default);
let mut vcpu_states = Vec::with_capacity(usize::from(config.max_vcpus));
vcpu_states.resize_with(usize::from(config.max_vcpus), VcpuState::default);
let device_manager = device_manager.lock().unwrap();
let cpuid = CpuManager::patch_cpuid(kvm)?;
let cpu_manager = Arc::new(Mutex::new(CpuManager {
boot_vcpus,
max_vcpus,
boot_vcpus: config.boot_vcpus,
max_vcpus: config.max_vcpus,
io_bus: device_manager.io_bus().clone(),
mmio_bus: device_manager.mmio_bus().clone(),
ioapic: device_manager.ioapic().clone(),
@@ -526,6 +719,7 @@ impl CpuManager {
vcpu_states,
reset_evt,
selected_cpu: 0,
vcpus: Vec::with_capacity(usize::from(config.max_vcpus)),
}));
device_manager
@@ -545,6 +739,153 @@ impl CpuManager {
Ok(cpu_manager)
}
fn patch_cpuid(kvm: &Kvm) -> Result<CpuId> {
let mut cpuid_patches = Vec::new();
// Patch tsc deadline timer bit
cpuid_patches.push(CpuidPatch {
function: 1,
index: 0,
flags_bit: None,
eax_bit: None,
ebx_bit: None,
ecx_bit: Some(TSC_DEADLINE_TIMER_ECX_BIT),
edx_bit: None,
});
// Patch hypervisor bit
cpuid_patches.push(CpuidPatch {
function: 1,
index: 0,
flags_bit: None,
eax_bit: None,
ebx_bit: None,
ecx_bit: Some(HYPERVISOR_ECX_BIT),
edx_bit: None,
});
// Supported CPUID
let mut cpuid = kvm
.get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES)
.map_err(Error::PatchCpuId)?;
CpuidPatch::patch_cpuid(&mut cpuid, cpuid_patches);
Ok(cpuid)
}
fn start_vcpu(
&mut self,
cpu_id: u8,
creation_ts: std::time::Instant,
vcpu_thread_barrier: Arc<Barrier>,
entry_point: Option<EntryPoint>,
inserting: bool,
snapshot: Option<Snapshot>,
) -> Result<()> {
let ioapic = if let Some(ioapic) = &self.ioapic {
Some(ioapic.clone())
} else {
None
};
let vcpu = Vcpu::new(
cpu_id,
&self.fd,
self.io_bus.clone(),
self.mmio_bus.clone(),
ioapic,
creation_ts,
)?;
let reset_evt = self.reset_evt.try_clone().unwrap();
let vcpu_kill_signalled = self.vcpus_kill_signalled.clone();
let vcpu_pause_signalled = self.vcpus_pause_signalled.clone();
let vcpu_kill = self.vcpu_states[usize::from(cpu_id)].kill.clone();
if let Some(snapshot) = snapshot {
let mut cpuid = self.cpuid.clone();
CpuidPatch::set_cpuid_reg(&mut cpuid, 0xb, None, CpuidReg::EDX, u32::from(cpu_id));
vcpu.lock()
.unwrap()
.fd
.set_cpuid2(&cpuid)
.map_err(Error::SetSupportedCpusFailed)?;
vcpu.lock()
.unwrap()
.restore(snapshot)
.expect("Failed to restore vCPU");
} else {
let vm_memory = self.vm_memory.clone();
vcpu.lock()
.unwrap()
.configure(entry_point, &vm_memory, self.cpuid.clone())
.expect("Failed to configure vCPU");
}
let vcpu_clone = Arc::clone(&vcpu);
self.vcpus.push(vcpu_clone);
let handle = Some(
thread::Builder::new()
.name(format!("vcpu{}", cpu_id))
.spawn(move || {
extern "C" fn handle_signal(_: i32, _: *mut siginfo_t, _: *mut c_void) {}
// This uses an async signal safe handler to kill the vcpu handles.
register_signal_handler(SIGRTMIN(), handle_signal)
.expect("Failed to register vcpu signal handler");
// Block until all CPUs are ready.
vcpu_thread_barrier.wait();
loop {
// vcpu.run() returns false on a KVM_EXIT_SHUTDOWN (triple-fault) so trigger a reset
match vcpu.lock().unwrap().run() {
Err(e) => {
error!("VCPU generated error: {:?}", e);
break;
}
Ok(true) => {}
Ok(false) => {
reset_evt.write(1).unwrap();
break;
}
}
// We've been told to terminate
if vcpu_kill_signalled.load(Ordering::SeqCst)
|| vcpu_kill.load(Ordering::SeqCst)
{
break;
}
// If we are being told to pause, we park the thread
// until the pause boolean is toggled.
// The resume operation is responsible for toggling
// the boolean and unpark the thread.
// We enter a loop because park() could spuriously
// return. We will then park() again unless the
// pause boolean has been toggled.
while vcpu_pause_signalled.load(Ordering::SeqCst) {
thread::park();
}
}
})
.map_err(Error::VcpuSpawn)?,
);
// On hot plug calls into this function entry_point is None. It is for
// those hotplug CPU additions that we need to set the inserting flag.
self.vcpu_states[usize::from(cpu_id)].handle = handle;
self.vcpu_states[usize::from(cpu_id)].inserting = inserting;
Ok(())
}
fn activate_vcpus(&mut self, desired_vcpus: u8, entry_point: Option<EntryPoint>) -> Result<()> {
if desired_vcpus > self.max_vcpus {
return Err(Error::DesiredVCPUCountExceedsMax);
@@ -556,86 +897,14 @@ impl CpuManager {
));
for cpu_id in self.present_vcpus()..desired_vcpus {
let ioapic = if let Some(ioapic) = &self.ioapic {
Some(ioapic.clone())
} else {
None
};
let mut vcpu = Vcpu::new(
self.start_vcpu(
cpu_id,
&self.fd,
self.io_bus.clone(),
self.mmio_bus.clone(),
ioapic,
creation_ts,
vcpu_thread_barrier.clone(),
entry_point,
entry_point.is_none(),
None,
)?;
let vcpu_thread_barrier = vcpu_thread_barrier.clone();
let reset_evt = self.reset_evt.try_clone().unwrap();
let vcpu_kill_signalled = self.vcpus_kill_signalled.clone();
let vcpu_pause_signalled = self.vcpus_pause_signalled.clone();
let vcpu_kill = self.vcpu_states[usize::from(cpu_id)].kill.clone();
let vm_memory = self.vm_memory.clone();
let cpuid = self.cpuid.clone();
let handle = Some(
thread::Builder::new()
.name(format!("vcpu{}", vcpu.id))
.spawn(move || {
extern "C" fn handle_signal(_: i32, _: *mut siginfo_t, _: *mut c_void) {}
// This uses an async signal safe handler to kill the vcpu handles.
register_signal_handler(SIGRTMIN(), handle_signal)
.expect("Failed to register vcpu signal handler");
vcpu.configure(entry_point, &vm_memory, cpuid)
.expect("Failed to configure vCPU");
// Block until all CPUs are ready.
vcpu_thread_barrier.wait();
loop {
// vcpu.run() returns false on a KVM_EXIT_SHUTDOWN (triple-fault) so trigger a reset
match vcpu.run() {
Err(e) => {
error!("VCPU generated error: {:?}", e);
break;
}
Ok(true) => {}
Ok(false) => {
reset_evt.write(1).unwrap();
break;
}
}
// We've been told to terminate
if vcpu_kill_signalled.load(Ordering::SeqCst)
|| vcpu_kill.load(Ordering::SeqCst)
{
break;
}
// If we are being told to pause, we park the thread
// until the pause boolean is toggled.
// The resume operation is responsible for toggling
// the boolean and unpark the thread.
// We enter a loop because park() could spuriously
// return. We will then park() again unless the
// pause boolean has been toggled.
while vcpu_pause_signalled.load(Ordering::SeqCst) {
thread::park();
}
}
})
.map_err(Error::VcpuSpawn)?,
);
// On hot plug calls into this function entry_point is None. It is for
// those hotplug CPU additions that we need to set the inserting flag.
self.vcpu_states[usize::from(cpu_id)].handle = handle;
self.vcpu_states[usize::from(cpu_id)].inserting = entry_point.is_none();
}
// Unblock all CPU threads.
@@ -1068,5 +1337,45 @@ impl Pausable for CpuManager {
}
}
impl Snapshotable for CpuManager {}
impl Snapshottable for CpuManager {
fn id(&self) -> String {
CPU_MANAGER_SNAPSHOT_ID.to_string()
}
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
let mut cpu_manager_snapshot = Snapshot::new(CPU_MANAGER_SNAPSHOT_ID);
// The CpuManager snapshot is a collection of all vCPUs snapshots.
for vcpu in &self.vcpus {
let cpu_snapshot = vcpu.lock().unwrap().snapshot()?;
cpu_manager_snapshot.add_snapshot(cpu_snapshot);
}
Ok(cpu_manager_snapshot)
}
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
let creation_ts = std::time::Instant::now();
let vcpu_thread_barrier = Arc::new(Barrier::new((snapshot.snapshots.len() + 1) as usize));
for (cpu_id, snapshot) in snapshot.snapshots.iter() {
debug!("Restoring VCPU {}", cpu_id);
self.start_vcpu(
cpu_id.parse::<u8>().unwrap(),
creation_ts,
vcpu_thread_barrier.clone(),
None,
false,
Some(*snapshot.clone()),
)
.map_err(|e| MigratableError::Restore(anyhow!("Could not restore vCPU {:?}", e)))?;
}
// Unblock all restored CPU threads.
vcpu_thread_barrier.wait();
Ok(())
}
}
impl Transportable for CpuManager {}
impl Migratable for CpuManager {}

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@
// SPDX-License-Identifier: Apache-2.0
//
extern crate anyhow;
extern crate arc_swap;
#[macro_use]
extern crate lazy_static;
@@ -13,19 +14,25 @@ extern crate serde;
extern crate serde_derive;
extern crate serde_json;
extern crate tempfile;
extern crate url;
extern crate vmm_sys_util;
use crate::api::{ApiError, ApiRequest, ApiResponse, ApiResponsePayload, VmInfo, VmmPingResponse};
use crate::config::{DeviceConfig, VmConfig};
use crate::config::{
DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, RestoreConfig, VmConfig, VsockConfig,
};
use crate::migration::{recv_vm_snapshot, vm_config_from_snapshot};
use crate::seccomp_filters::{get_seccomp_filter, Thread};
use crate::vm::{Error as VmError, Vm, VmState};
use libc::EFD_NONBLOCK;
use seccomp::{SeccompFilter, SeccompLevel};
use std::io;
use std::os::unix::io::{AsRawFd, RawFd};
use std::path::PathBuf;
use std::sync::mpsc::{Receiver, RecvError, SendError, Sender};
use std::sync::{Arc, Mutex};
use std::{result, thread};
use vm_device::Pausable;
use vm_migration::{Pausable, Snapshottable, Transportable};
use vmm_sys_util::eventfd::EventFd;
pub mod api;
@@ -34,6 +41,8 @@ pub mod cpu;
pub mod device_manager;
pub mod interrupt;
pub mod memory_manager;
pub mod migration;
pub mod seccomp_filters;
pub mod vm;
#[cfg(feature = "acpi")]
@@ -84,6 +93,12 @@ pub enum Error {
// Error following "exe" link
ExePathReadLink(io::Error),
/// Cannot create seccomp filter
CreateSeccompFilter(seccomp::SeccompError),
/// Cannot apply seccomp filter
ApplySeccompFilter(seccomp::Error),
}
pub type Result<T> = result::Result<T, Error>;
@@ -161,9 +176,14 @@ pub fn start_vmm_thread(
api_event: EventFd,
api_sender: Sender<ApiRequest>,
api_receiver: Receiver<ApiRequest>,
seccomp_level: &SeccompLevel,
) -> Result<thread::JoinHandle<Result<()>>> {
let http_api_event = api_event.try_clone().map_err(Error::EventFdClone)?;
// Retrieve seccomp filter
let vmm_seccomp_filter =
get_seccomp_filter(seccomp_level, Thread::Vmm).map_err(Error::CreateSeccompFilter)?;
// Find the path that the "/proc/<pid>/exe" symlink points to. Must be done before spawning
// a thread as Rust does not put the child threads in the same thread group which prevents the
// link from being followed as per PTRACE_MODE_READ_FSCREDS (see proc(5) and ptrace(2)). The
@@ -173,6 +193,9 @@ pub fn start_vmm_thread(
let thread = thread::Builder::new()
.name("vmm".to_string())
.spawn(move || {
// Apply seccomp filter for VMM thread.
SeccompFilter::apply(vmm_seccomp_filter).map_err(Error::ApplySeccompFilter)?;
let mut vmm = Vmm::new(vmm_version.to_string(), api_event, vmm_path)?;
vmm.control_loop(Arc::new(api_receiver))
@@ -180,7 +203,7 @@ pub fn start_vmm_thread(
.map_err(Error::VmmThreadSpawn)?;
// The VMM thread is started, we can start serving HTTP requests
api::start_http_thread(http_path, http_api_event, api_sender)?;
api::start_http_thread(http_path, http_api_event, api_sender, seccomp_level)?;
Ok(thread)
}
@@ -271,6 +294,57 @@ impl Vmm {
}
}
fn vm_snapshot(&mut self, destination_url: &str) -> result::Result<(), VmError> {
if let Some(ref mut vm) = self.vm {
vm.snapshot()
.map_err(VmError::Snapshot)
.and_then(|snapshot| {
vm.send(&snapshot, destination_url)
.map_err(VmError::SnapshotSend)
})
} else {
Err(VmError::VmNotRunning)
}
}
fn vm_restore(&mut self, restore_cfg: RestoreConfig) -> result::Result<(), VmError> {
if self.vm.is_some() || self.vm_config.is_some() {
return Err(VmError::VmAlreadyCreated);
}
let source_url = restore_cfg.source_url.as_path().to_str();
if source_url.is_none() {
return Err(VmError::RestoreSourceUrlPathToStr);
}
// Safe to unwrap as we checked it was Some(&str).
let source_url = source_url.unwrap();
let vm_snapshot = recv_vm_snapshot(source_url).map_err(VmError::Restore)?;
let vm_config = vm_config_from_snapshot(&vm_snapshot).map_err(VmError::Restore)?;
self.vm_config = Some(Arc::clone(&vm_config));
let exit_evt = self.exit_evt.try_clone().map_err(VmError::EventFdClone)?;
let reset_evt = self.reset_evt.try_clone().map_err(VmError::EventFdClone)?;
let vm = Vm::new_from_snapshot(
&vm_snapshot,
exit_evt,
reset_evt,
self.vmm_path.clone(),
source_url,
restore_cfg.prefault,
)?;
self.vm = Some(vm);
// Now we can restore the rest of the VM.
if let Some(ref mut vm) = self.vm {
vm.restore(vm_snapshot).map_err(VmError::Restore)
} else {
Err(VmError::VmNotCreated)
}
}
fn vm_shutdown(&mut self) -> result::Result<(), VmError> {
if let Some(ref mut vm) = self.vm.take() {
vm.shutdown()
@@ -388,7 +462,72 @@ impl Vmm {
fn vm_remove_device(&mut self, id: String) -> result::Result<(), VmError> {
if let Some(ref mut vm) = self.vm {
if let Err(e) = vm.remove_device(id) {
error!("Error when adding new device to the VM: {:?}", e);
error!("Error when removing new device to the VM: {:?}", e);
Err(e)
} else {
Ok(())
}
} else {
Err(VmError::VmNotRunning)
}
}
fn vm_add_disk(&mut self, disk_cfg: DiskConfig) -> result::Result<(), VmError> {
if let Some(ref mut vm) = self.vm {
if let Err(e) = vm.add_disk(disk_cfg) {
error!("Error when adding new disk to the VM: {:?}", e);
Err(e)
} else {
Ok(())
}
} else {
Err(VmError::VmNotRunning)
}
}
fn vm_add_fs(&mut self, fs_cfg: FsConfig) -> result::Result<(), VmError> {
if let Some(ref mut vm) = self.vm {
if let Err(e) = vm.add_fs(fs_cfg) {
error!("Error when adding new fs to the VM: {:?}", e);
Err(e)
} else {
Ok(())
}
} else {
Err(VmError::VmNotRunning)
}
}
fn vm_add_pmem(&mut self, pmem_cfg: PmemConfig) -> result::Result<(), VmError> {
if let Some(ref mut vm) = self.vm {
if let Err(e) = vm.add_pmem(pmem_cfg) {
error!("Error when adding new pmem device to the VM: {:?}", e);
Err(e)
} else {
Ok(())
}
} else {
Err(VmError::VmNotRunning)
}
}
fn vm_add_net(&mut self, net_cfg: NetConfig) -> result::Result<(), VmError> {
if let Some(ref mut vm) = self.vm {
if let Err(e) = vm.add_net(net_cfg) {
error!("Error when adding new network device to the VM: {:?}", e);
Err(e)
} else {
Ok(())
}
} else {
Err(VmError::VmNotRunning)
}
}
fn vm_add_vsock(&mut self, vsock_cfg: VsockConfig) -> result::Result<(), VmError> {
if let Some(ref mut vm) = self.vm {
if let Err(e) = vm.add_vsock(vsock_cfg) {
error!("Error when adding new vsock device to the VM: {:?}", e);
Err(e)
} else {
Ok(())
@@ -533,6 +672,22 @@ impl Vmm {
sender.send(response).map_err(Error::ApiResponseSend)?;
}
ApiRequest::VmSnapshot(snapshot_data, sender) => {
let response = self
.vm_snapshot(&snapshot_data.destination_url)
.map_err(ApiError::VmSnapshot)
.map(|_| ApiResponsePayload::Empty);
sender.send(response).map_err(Error::ApiResponseSend)?;
}
ApiRequest::VmRestore(restore_data, sender) => {
let response = self
.vm_restore(restore_data.as_ref().clone())
.map_err(ApiError::VmRestore)
.map(|_| ApiResponsePayload::Empty);
sender.send(response).map_err(Error::ApiResponseSend)?;
}
ApiRequest::VmmShutdown(sender) => {
let response = self
.vmm_shutdown()
@@ -567,6 +722,41 @@ impl Vmm {
.map(|_| ApiResponsePayload::Empty);
sender.send(response).map_err(Error::ApiResponseSend)?;
}
ApiRequest::VmAddDisk(add_disk_data, sender) => {
let response = self
.vm_add_disk(add_disk_data.as_ref().clone())
.map_err(ApiError::VmAddDisk)
.map(|_| ApiResponsePayload::Empty);
sender.send(response).map_err(Error::ApiResponseSend)?;
}
ApiRequest::VmAddFs(add_fs_data, sender) => {
let response = self
.vm_add_fs(add_fs_data.as_ref().clone())
.map_err(ApiError::VmAddFs)
.map(|_| ApiResponsePayload::Empty);
sender.send(response).map_err(Error::ApiResponseSend)?;
}
ApiRequest::VmAddPmem(add_pmem_data, sender) => {
let response = self
.vm_add_pmem(add_pmem_data.as_ref().clone())
.map_err(ApiError::VmAddPmem)
.map(|_| ApiResponsePayload::Empty);
sender.send(response).map_err(Error::ApiResponseSend)?;
}
ApiRequest::VmAddNet(add_net_data, sender) => {
let response = self
.vm_add_net(add_net_data.as_ref().clone())
.map_err(ApiError::VmAddNet)
.map(|_| ApiResponsePayload::Empty);
sender.send(response).map_err(Error::ApiResponseSend)?;
}
ApiRequest::VmAddVsock(add_vsock_data, sender) => {
let response = self
.vm_add_vsock(add_vsock_data.as_ref().clone())
.map_err(ApiError::VmAddVsock)
.map(|_| ApiResponsePayload::Empty);
sender.send(response).map_err(Error::ApiResponseSend)?;
}
}
}
}
@@ -577,3 +767,7 @@ impl Vmm {
Ok(())
}
}
const CPU_MANAGER_SNAPSHOT_ID: &str = "cpu-manager";
const MEMORY_MANAGER_SNAPSHOT_ID: &str = "memory-manager";
const DEVICE_MANAGER_SNAPSHOT_ID: &str = "device-manager";

View File

@@ -3,25 +3,37 @@
// SPDX-License-Identifier: Apache-2.0
//
use crate::config::{HotplugMethod, MemoryConfig};
use crate::MEMORY_MANAGER_SNAPSHOT_ID;
#[cfg(feature = "acpi")]
use acpi_tables::{aml, aml::Aml};
use arch::RegionType;
use devices::BusDevice;
use kvm_bindings::kvm_userspace_memory_region;
use anyhow::anyhow;
use arch::{layout, RegionType};
use devices::{ioapic, BusDevice};
use kvm_bindings::{kvm_userspace_memory_region, KVM_MEM_READONLY};
use kvm_ioctls::*;
use std::convert::TryInto;
use std::ffi;
use std::fs::{File, OpenOptions};
use std::io;
use std::os::unix::io::FromRawFd;
use std::os::unix::io::{FromRawFd, RawFd};
use std::path::PathBuf;
use std::result;
use std::sync::{Arc, Mutex};
use vm_allocator::SystemAllocator;
use url::Url;
use vm_allocator::{GsiApic, SystemAllocator};
use vm_memory::guest_memory::FileOffset;
use vm_memory::{
mmap::MmapRegionError, Address, Error as MmapError, GuestAddress, GuestAddressSpace,
GuestMemory, GuestMemoryAtomic, GuestMemoryMmap, GuestMemoryRegion, GuestRegionMmap,
GuestUsize, MmapRegion,
mmap::MmapRegionError, Address, Bytes, Error as MmapError, GuestAddress, GuestAddressSpace,
GuestMemory, GuestMemoryAtomic, GuestMemoryLoadGuard, GuestMemoryMmap, GuestMemoryRegion,
GuestRegionMmap, GuestUsize, MemoryRegionAddress, MmapRegion,
};
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
Transportable,
};
const X86_64_IRQ_BASE: u32 = 5;
const HOTPLUG_COUNT: usize = 8;
@@ -45,8 +57,15 @@ pub struct MemoryManager {
backing_file: Option<PathBuf>,
mergeable: bool,
allocator: Arc<Mutex<SystemAllocator>>,
hotplug_method: HotplugMethod,
boot_ram: u64,
current_ram: u64,
next_hotplug_slot: usize,
pub virtiomem_region: Option<Arc<GuestRegionMmap>>,
pub virtiomem_resize: Option<vm_virtio::Resize>,
snapshot: Mutex<Option<GuestMemoryLoadGuard<GuestMemoryMmap>>>,
shared: bool,
hugepages: bool,
}
#[derive(Debug)]
@@ -80,6 +99,25 @@ pub enum Error {
/// Failed to set the user memory region.
SetUserMemoryRegion(kvm_ioctls::Error),
/// Failed to EventFd.
EventFdFail(io::Error),
/// Eventfd write error
EventfdError(io::Error),
/// Failed to virtio-mem resize
VirtioMemResizeFail(vm_virtio::mem::Error),
/// Cannot restore VM
Restore(MigratableError),
/// Cannot create the system allocator
CreateSystemAllocator,
/// The number of external backing files doesn't match the number of
/// memory regions.
InvalidAmountExternalBackingFiles,
}
pub fn get_host_cpu_phys_bits() -> u8 {
@@ -192,15 +230,13 @@ impl BusDevice for MemoryManager {
impl MemoryManager {
pub fn new(
allocator: Arc<Mutex<SystemAllocator>>,
fd: Arc<VmFd>,
boot_ram: u64,
hotplug_size: Option<u64>,
backing_file: &Option<PathBuf>,
mergeable: bool,
config: &MemoryConfig,
ext_regions: Option<Vec<MemoryRegion>>,
prefault: bool,
) -> Result<Arc<Mutex<MemoryManager>>, Error> {
// Init guest memory
let arch_mem_regions = arch::arch_memory_regions(boot_ram);
let arch_mem_regions = arch::arch_memory_regions(config.size);
let ram_regions: Vec<(GuestAddress, usize)> = arch_mem_regions
.iter()
@@ -209,12 +245,34 @@ impl MemoryManager {
.collect();
let mut mem_regions = Vec::new();
for region in ram_regions.iter() {
mem_regions.push(MemoryManager::create_ram_region(
backing_file,
region.0,
region.1,
)?);
if let Some(ext_regions) = &ext_regions {
if ram_regions.len() > ext_regions.len() {
return Err(Error::InvalidAmountExternalBackingFiles);
}
for region in ext_regions.iter() {
mem_regions.push(MemoryManager::create_ram_region(
&Some(region.backing_file.clone()),
region.start_addr,
region.size as usize,
true,
prefault,
false,
false,
)?);
}
} else {
for region in ram_regions.iter() {
mem_regions.push(MemoryManager::create_ram_region(
&config.file,
region.0,
region.1,
false,
prefault,
config.shared,
config.hugepages,
)?);
}
}
let guest_memory =
@@ -228,8 +286,32 @@ impl MemoryManager {
mem_end.unchecked_add(1)
};
if let Some(size) = hotplug_size {
start_of_device_area = start_of_device_area.unchecked_add(size);
let mut virtiomem_region = None;
let mut virtiomem_resize = None;
if let Some(size) = config.hotplug_size {
if config.hotplug_method == HotplugMethod::VirtioMem {
// Alignment must be "natural" i.e. same as size of block
let start_addr = GuestAddress(
(start_of_device_area.0 + vm_virtio::VIRTIO_MEM_DEFAULT_BLOCK_SIZE - 1)
/ vm_virtio::VIRTIO_MEM_DEFAULT_BLOCK_SIZE
* vm_virtio::VIRTIO_MEM_DEFAULT_BLOCK_SIZE,
);
virtiomem_region = Some(MemoryManager::create_ram_region(
&config.file,
start_addr,
size as usize,
false,
false,
config.shared,
config.hugepages,
)?);
virtiomem_resize = Some(vm_virtio::Resize::new().map_err(Error::EventFdFail)?);
start_of_device_area = start_addr.unchecked_add(size);
} else {
start_of_device_area = start_of_device_area.unchecked_add(size);
}
}
let guest_memory = GuestMemoryAtomic::new(guest_memory);
@@ -237,6 +319,23 @@ impl MemoryManager {
let mut hotplug_slots = Vec::with_capacity(HOTPLUG_COUNT);
hotplug_slots.resize_with(HOTPLUG_COUNT, HotPlugState::default);
// Let's allocate 64 GiB of addressable MMIO space, starting at 0.
let allocator = Arc::new(Mutex::new(
SystemAllocator::new(
GuestAddress(0),
1 << 16 as GuestUsize,
GuestAddress(0),
1 << get_host_cpu_phys_bits(),
layout::MEM_32BIT_RESERVED_START,
layout::MEM_32BIT_DEVICES_SIZE,
vec![GsiApic::new(
X86_64_IRQ_BASE,
ioapic::NUM_IOAPIC_PINS as u32 - X86_64_IRQ_BASE,
)],
)
.ok_or(Error::CreateSystemAllocator)?,
));
let memory_manager = Arc::new(Mutex::new(MemoryManager {
guest_memory: guest_memory.clone(),
next_kvm_memory_slot: 0,
@@ -245,11 +344,18 @@ impl MemoryManager {
fd,
hotplug_slots,
selected_slot: 0,
backing_file: backing_file.clone(),
mergeable,
backing_file: config.file.clone(),
mergeable: config.mergeable,
allocator: allocator.clone(),
current_ram: boot_ram,
hotplug_method: config.hotplug_method.clone(),
boot_ram: config.size,
current_ram: config.size,
next_hotplug_slot: 0,
virtiomem_region: virtiomem_region.clone(),
virtiomem_resize,
snapshot: Mutex::new(None),
shared: config.shared,
hugepages: config.hugepages,
}));
guest_memory.memory().with_regions(|_, region| {
@@ -257,11 +363,27 @@ impl MemoryManager {
region.start_addr().raw_value(),
region.len() as u64,
region.as_ptr() as u64,
mergeable,
config.mergeable,
false,
)?;
Ok(())
})?;
if let Some(region) = virtiomem_region {
memory_manager.lock().unwrap().create_userspace_mapping(
region.start_addr().raw_value(),
region.len() as u64,
region.as_ptr() as u64,
config.mergeable,
false,
)?;
allocator
.lock()
.unwrap()
.allocate_mmio_addresses(Some(region.start_addr()), region.len(), None)
.ok_or(Error::MemoryRangeAllocation)?;
}
// Allocate RAM and Reserved address ranges.
for region in arch_mem_regions.iter() {
allocator
@@ -274,16 +396,106 @@ impl MemoryManager {
Ok(memory_manager)
}
pub fn new_from_snapshot(
snapshot: &Snapshot,
fd: Arc<VmFd>,
config: &MemoryConfig,
source_url: &str,
prefault: bool,
) -> Result<Arc<Mutex<MemoryManager>>, Error> {
let url = Url::parse(source_url).unwrap();
/* url must be valid dir which is verified in recv_vm_snapshot() */
let vm_snapshot_path = url.to_file_path().unwrap();
if let Some(mem_section) = snapshot
.snapshot_data
.get(&format!("{}-section", MEMORY_MANAGER_SNAPSHOT_ID))
{
let mem_snapshot: MemoryManagerSnapshotData =
match serde_json::from_slice(&mem_section.snapshot) {
Ok(snapshot) => snapshot,
Err(error) => {
return Err(Error::Restore(MigratableError::Restore(anyhow!(
"Could not deserialize MemoryManager {}",
error
))))
}
};
let mut ext_regions = mem_snapshot.memory_regions;
for region in ext_regions.iter_mut() {
let mut memory_region_path = vm_snapshot_path.clone();
memory_region_path.push(region.backing_file.clone());
region.backing_file = memory_region_path;
}
// In case there was no backing file, we can safely use CoW by
// mapping the source files provided for restoring. This case
// allows for a faster VM restoration and does not require us to
// fill the memory content, hence we can return right away.
if config.file.is_none() {
return MemoryManager::new(fd, config, Some(ext_regions), prefault);
};
let memory_manager = MemoryManager::new(fd, config, None, false)?;
let guest_memory = memory_manager.lock().unwrap().guest_memory();
// In case the previous config was using a backing file, this means
// it was MAP_SHARED, therefore we must copy the content into the
// new regions so that we can still use MAP_SHARED when restoring
// the VM.
guest_memory.memory().with_regions(|index, region| {
// Open (read only) the snapshot file for the given region.
let mut memory_region_file = OpenOptions::new()
.read(true)
.open(&ext_regions[index].backing_file)
.map_err(|e| Error::Restore(MigratableError::MigrateReceive(e.into())))?;
// Fill the region with the file content.
region
.read_from(
MemoryRegionAddress(0),
&mut memory_region_file,
region.len().try_into().unwrap(),
)
.map_err(|e| Error::Restore(MigratableError::MigrateReceive(e.into())))?;
Ok(())
})?;
Ok(memory_manager)
} else {
Err(Error::Restore(MigratableError::Restore(anyhow!(
"Could not find {}-section from snapshot",
MEMORY_MANAGER_SNAPSHOT_ID
))))
}
}
fn memfd_create(name: &ffi::CStr, flags: u32) -> Result<RawFd, io::Error> {
let res = unsafe { libc::syscall(libc::SYS_memfd_create, name.as_ptr(), flags) };
if res < 0 {
Err(io::Error::last_os_error())
} else {
Ok(res as RawFd)
}
}
fn create_ram_region(
backing_file: &Option<PathBuf>,
start_addr: GuestAddress,
size: usize,
copy_on_write: bool,
prefault: bool,
shared: bool,
hugepages: bool,
) -> Result<Arc<GuestRegionMmap>, Error> {
Ok(Arc::new(match backing_file {
Some(ref file) => {
let f = if file.is_dir() {
let fs_str = format!("{}{}", file.display(), "/tmpfile_XXXXXX");
let fs = std::ffi::CString::new(fs_str).unwrap();
let fs = ffi::CString::new(fs_str).unwrap();
let mut path = fs.as_bytes_with_nul().to_owned();
let path_ptr = path.as_mut_ptr() as *mut _;
let fd = unsafe { libc::mkstemp(path_ptr) };
@@ -299,22 +511,74 @@ impl MemoryManager {
f.set_len(size as u64).map_err(Error::SharedFileSetLen)?;
let mut mmap_flags = if copy_on_write {
libc::MAP_NORESERVE | libc::MAP_PRIVATE
} else {
libc::MAP_NORESERVE | libc::MAP_SHARED
};
if prefault {
mmap_flags |= libc::MAP_POPULATE;
}
GuestRegionMmap::new(
MmapRegion::from_file(FileOffset::new(f, 0), size)
.map_err(Error::GuestMemoryRegion)?,
MmapRegion::build(
Some(FileOffset::new(f, 0)),
size,
libc::PROT_READ | libc::PROT_WRITE,
mmap_flags,
)
.map_err(Error::GuestMemoryRegion)?,
start_addr,
)
.map_err(Error::GuestMemory)?
}
None => {
let fd = Self::memfd_create(
&ffi::CString::new("ch_ram").unwrap(),
if hugepages {
libc::MFD_HUGETLB | libc::MAP_HUGE_2MB as u32
} else {
0
},
)
.map_err(Error::SharedFileCreate)?;
let f = unsafe { File::from_raw_fd(fd) };
f.set_len(size as u64).map_err(Error::SharedFileSetLen)?;
let mmap_flags = libc::MAP_NORESERVE
| if shared {
libc::MAP_SHARED
} else {
libc::MAP_PRIVATE
};
GuestRegionMmap::new(
MmapRegion::build(
Some(FileOffset::new(f, 0)),
size,
libc::PROT_READ | libc::PROT_WRITE,
mmap_flags,
)
.map_err(Error::GuestMemoryRegion)?,
start_addr,
)
.map_err(Error::GuestMemory)?
}
None => GuestRegionMmap::new(
MmapRegion::new(size).map_err(Error::GuestMemoryRegion)?,
start_addr,
)
.map_err(Error::GuestMemory)?,
}))
}
fn hotplug_ram_region(&mut self, size: usize) -> Result<(), Error> {
// Update the GuestMemoryMmap with the new range
fn add_region(&mut self, region: Arc<GuestRegionMmap>) -> Result<(), Error> {
let guest_memory = self
.guest_memory
.memory()
.insert_region(region)
.map_err(Error::GuestMemory)?;
self.guest_memory.lock().unwrap().replace(guest_memory);
Ok(())
}
fn hotplug_ram_region(&mut self, size: usize) -> Result<Arc<GuestRegionMmap>, Error> {
info!("Hotplugging new RAM: {}", size);
// Check that there is a free slot
@@ -342,7 +606,15 @@ impl MemoryManager {
}
// Allocate memory for the region
let region = MemoryManager::create_ram_region(&self.backing_file, start_addr, size)?;
let region = MemoryManager::create_ram_region(
&self.backing_file,
start_addr,
size,
false,
false,
self.shared,
self.hugepages,
)?;
// Map it into the guest
self.create_userspace_mapping(
@@ -350,6 +622,7 @@ impl MemoryManager {
region.len() as u64,
region.as_ptr() as u64,
self.mergeable,
false,
)?;
// Tell the allocator
@@ -368,21 +641,19 @@ impl MemoryManager {
self.next_hotplug_slot += 1;
// Update the GuestMemoryMmap with the new range
let guest_memory = self
.guest_memory
.memory()
.insert_region(region)
.map_err(Error::GuestMemory)?;
self.guest_memory.lock().unwrap().replace(guest_memory);
self.add_region(Arc::clone(&region))?;
Ok(())
Ok(region)
}
pub fn guest_memory(&self) -> GuestMemoryAtomic<GuestMemoryMmap> {
self.guest_memory.clone()
}
pub fn allocator(&self) -> Arc<Mutex<SystemAllocator>> {
self.allocator.clone()
}
pub fn start_of_device_area(&self) -> GuestAddress {
self.start_of_device_area
}
@@ -403,6 +674,7 @@ impl MemoryManager {
memory_size: u64,
userspace_addr: u64,
mergeable: bool,
readonly: bool,
) -> Result<u32, Error> {
let slot = self.allocate_kvm_memory_slot();
let mem_region = kvm_userspace_memory_region {
@@ -410,7 +682,7 @@ impl MemoryManager {
guest_phys_addr,
memory_size,
userspace_addr,
flags: 0,
flags: if readonly { KVM_MEM_READONLY } else { 0 },
};
// Safe because the guest regions are guaranteed not to overlap.
@@ -450,14 +722,97 @@ impl MemoryManager {
Ok(slot)
}
pub fn resize(&mut self, desired_ram: u64) -> Result<bool, Error> {
if desired_ram > self.current_ram {
self.hotplug_ram_region((desired_ram - self.current_ram) as usize)?;
self.current_ram = desired_ram;
Ok(true)
} else {
Ok(false)
pub fn remove_userspace_mapping(
&mut self,
guest_phys_addr: u64,
memory_size: u64,
userspace_addr: u64,
mergeable: bool,
slot: u32,
) -> Result<(), Error> {
let mem_region = kvm_userspace_memory_region {
slot,
guest_phys_addr,
memory_size: 0,
userspace_addr,
flags: 0,
};
// Safe to remove because we know the region exist.
unsafe { self.fd.set_user_memory_region(mem_region) }
.map_err(Error::SetUserMemoryRegion)?;
// Mark the pages as unmergeable if there were previously marked as
// mergeable.
if mergeable {
// Safe because the address and size are valid as the region was
// previously advised.
let ret = unsafe {
libc::madvise(
userspace_addr as *mut libc::c_void,
memory_size as libc::size_t,
libc::MADV_UNMERGEABLE,
)
};
if ret != 0 {
let err = io::Error::last_os_error();
// Safe to unwrap because the error is constructed with
// last_os_error(), which ensures the output will be Some().
let errno = err.raw_os_error().unwrap();
if errno == libc::EINVAL {
warn!("kernel not configured with CONFIG_KSM");
} else {
warn!("madvise error: {}", err);
}
warn!("failed to mark pages as unmergeable");
}
}
info!(
"Removed userspace mapping: {:x} -> {:x} {:x}",
guest_phys_addr, userspace_addr, memory_size
);
Ok(())
}
pub fn virtiomem_resize(&mut self, size: u64) -> Result<(), Error> {
let region = self.virtiomem_region.take();
if let Some(region) = region {
self.add_region(region)?;
}
if let Some(resize) = &self.virtiomem_resize {
resize.work(size).map_err(Error::VirtioMemResizeFail)?;
} else {
panic!("should not fail here");
}
Ok(())
}
/// In case this function resulted in adding a new memory region to the
/// guest memory, the new region is returned to the caller. The virtio-mem
/// use case never adds a new region as the whole hotpluggable memory has
/// already been allocated at boot time.
pub fn resize(&mut self, desired_ram: u64) -> Result<Option<Arc<GuestRegionMmap>>, Error> {
let mut region: Option<Arc<GuestRegionMmap>> = None;
match self.hotplug_method {
HotplugMethod::VirtioMem => {
if desired_ram >= self.boot_ram {
self.virtiomem_resize(desired_ram - self.boot_ram)?;
self.current_ram = desired_ram;
}
}
HotplugMethod::Acpi => {
if desired_ram >= self.current_ram {
region =
Some(self.hotplug_ram_region((desired_ram - self.current_ram) as usize)?);
self.current_ram = desired_ram;
}
}
}
Ok(region)
}
}
@@ -794,3 +1149,128 @@ impl Aml for MemoryManager {
bytes
}
}
impl Pausable for MemoryManager {}
#[derive(Serialize, Deserialize)]
#[serde(remote = "GuestAddress")]
pub struct GuestAddressDef(pub u64);
#[derive(Serialize, Deserialize)]
pub struct MemoryRegion {
backing_file: PathBuf,
#[serde(with = "GuestAddressDef")]
start_addr: GuestAddress,
size: GuestUsize,
}
#[derive(Serialize, Deserialize)]
pub struct MemoryManagerSnapshotData {
memory_regions: Vec<MemoryRegion>,
}
impl Snapshottable for MemoryManager {
fn id(&self) -> String {
MEMORY_MANAGER_SNAPSHOT_ID.to_string()
}
fn snapshot(&self) -> result::Result<Snapshot, MigratableError> {
let mut memory_manager_snapshot = Snapshot::new(MEMORY_MANAGER_SNAPSHOT_ID);
let guest_memory = self.guest_memory.memory();
let mut memory_regions: Vec<MemoryRegion> = Vec::with_capacity(10);
guest_memory.with_regions_mut(|index, region| {
if region.len() == 0 {
return Err(MigratableError::Snapshot(anyhow!("Zero length region")));
}
memory_regions.push(MemoryRegion {
backing_file: PathBuf::from(format!("memory-region-{}", index)),
start_addr: region.start_addr(),
size: region.len(),
});
Ok(())
})?;
let snapshot_data_section =
serde_json::to_vec(&MemoryManagerSnapshotData { memory_regions })
.map_err(|e| MigratableError::Snapshot(e.into()))?;
memory_manager_snapshot.add_data_section(SnapshotDataSection {
id: format!("{}-section", MEMORY_MANAGER_SNAPSHOT_ID),
snapshot: snapshot_data_section,
});
let mut memory_snapshot = self.snapshot.lock().unwrap();
*memory_snapshot = Some(guest_memory);
Ok(memory_manager_snapshot)
}
}
impl Transportable for MemoryManager {
fn send(
&self,
_snapshot: &Snapshot,
destination_url: &str,
) -> result::Result<(), MigratableError> {
let url = Url::parse(destination_url).map_err(|e| {
MigratableError::MigrateSend(anyhow!("Could not parse destination URL: {}", e))
})?;
match url.scheme() {
"file" => {
let vm_memory_snapshot_path = url
.to_file_path()
.map_err(|_| {
MigratableError::MigrateSend(anyhow!(
"Could not convert file URL to a file path"
))
})
.and_then(|path| {
if !path.is_dir() {
return Err(MigratableError::MigrateSend(anyhow!(
"Destination is not a directory"
)));
}
Ok(path)
})?;
if let Some(guest_memory) = &*self.snapshot.lock().unwrap() {
guest_memory.with_regions_mut(|index, region| {
let mut memory_region_path = vm_memory_snapshot_path.clone();
memory_region_path.push(format!("memory-region-{}", index));
// Create the snapshot file for the region
let mut memory_region_file = OpenOptions::new()
.read(true)
.write(true)
.create_new(true)
.open(memory_region_path)
.map_err(|e| MigratableError::MigrateSend(e.into()))?;
guest_memory
.write_to(
region.start_addr(),
&mut memory_region_file,
region.len().try_into().unwrap(),
)
.map_err(|e| MigratableError::MigrateSend(e.into()))?;
Ok(())
})?;
}
}
_ => {
return Err(MigratableError::MigrateSend(anyhow!(
"Unsupported VM transport URL scheme: {}",
url.scheme()
)))
}
}
Ok(())
}
}
impl Migratable for MemoryManager {}

84
vmm/src/migration.rs Normal file
View File

@@ -0,0 +1,84 @@
// Copyright © 2020 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use crate::config::VmConfig;
use crate::vm::{VmSnapshot, VM_SNAPSHOT_ID};
use anyhow::anyhow;
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use url::Url;
use vm_migration::{MigratableError, Snapshot};
pub const VM_SNAPSHOT_FILE: &str = "vm.json";
pub fn url_to_path(url: &Url) -> std::result::Result<PathBuf, MigratableError> {
match url.scheme() {
"file" => url
.to_file_path()
.map_err(|_| {
MigratableError::MigrateSend(anyhow!("Could not convert file URL to a file path"))
})
.and_then(|path| {
if !path.is_dir() {
return Err(MigratableError::MigrateSend(anyhow!(
"Destination is not a directory"
)));
}
Ok(path)
}),
_ => Err(MigratableError::MigrateSend(anyhow!(
"URL scheme is not file: {}",
url.scheme()
))),
}
}
pub fn recv_vm_snapshot(source_url: &str) -> std::result::Result<Snapshot, MigratableError> {
let url = Url::parse(source_url).map_err(|e| {
MigratableError::MigrateSend(anyhow!("Could not parse destination URL: {}", e))
})?;
match url.scheme() {
"file" => {
let mut vm_snapshot_path = url_to_path(&url)?;
vm_snapshot_path.push(VM_SNAPSHOT_FILE);
// Try opening the snapshot file
let vm_snapshot_file =
File::open(vm_snapshot_path).map_err(|e| MigratableError::MigrateSend(e.into()))?;
let vm_snapshot_reader = BufReader::new(vm_snapshot_file);
let vm_snapshot = serde_json::from_reader(vm_snapshot_reader)
.map_err(|e| MigratableError::MigrateReceive(e.into()))?;
Ok(vm_snapshot)
}
_ => Err(MigratableError::MigrateSend(anyhow!(
"Unsupported VM transport URL scheme: {}",
url.scheme()
))),
}
}
pub fn vm_config_from_snapshot(
snapshot: &Snapshot,
) -> std::result::Result<Arc<Mutex<VmConfig>>, MigratableError> {
if let Some(vm_section) = snapshot
.snapshot_data
.get(&format!("{}-section", VM_SNAPSHOT_ID))
{
let vm_snapshot: VmSnapshot =
serde_json::from_slice(&vm_section.snapshot).map_err(|e| {
MigratableError::Restore(anyhow!("Could not deserialize VM snapshot {}", e))
})?;
return Ok(vm_snapshot.config);
}
Err(MigratableError::Restore(anyhow!(
"Could not find VM config snapshot section"
)))
}

322
vmm/src/seccomp_filters.rs Normal file
View File

@@ -0,0 +1,322 @@
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Copyright © 2020 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use libc;
use seccomp::{
allow_syscall, allow_syscall_if, BpfProgram, Error, SeccompAction, SeccompCmpArgLen as ArgLen,
SeccompCmpOp::Eq, SeccompCondition as Cond, SeccompError, SeccompFilter, SeccompLevel,
SeccompRule,
};
use std::convert::TryInto;
pub enum Thread {
Vmm,
Api,
}
/// Shorthand for chaining `SeccompCondition`s with the `and` operator in a `SeccompRule`.
/// The rule will take the `Allow` action if _all_ the conditions are true.
///
/// [`Allow`]: enum.SeccompAction.html
/// [`SeccompCondition`]: struct.SeccompCondition.html
/// [`SeccompRule`]: struct.SeccompRule.html
macro_rules! and {
($($x:expr,)*) => (SeccompRule::new(vec![$($x),*], SeccompAction::Allow));
($($x:expr),*) => (SeccompRule::new(vec![$($x),*], SeccompAction::Allow))
}
/// Shorthand for chaining `SeccompRule`s with the `or` operator in a `SeccompFilter`.
///
/// [`SeccompFilter`]: struct.SeccompFilter.html
/// [`SeccompRule`]: struct.SeccompRule.html
macro_rules! or {
($($x:expr,)*) => (vec![$($x),*]);
($($x:expr),*) => (vec![$($x),*])
}
// See include/uapi/asm-generic/ioctls.h in the kernel code.
const TCGETS: u64 = 0x5401;
const TCSETS: u64 = 0x5402;
const TIOCGWINSZ: u64 = 0x5413;
const FIOCLEX: u64 = 0x5451;
const FIONBIO: u64 = 0x5421;
// See include/uapi/linux/kvm.h in the kernel code.
const KVM_GET_API_VERSION: u64 = 0xae00;
const KVM_CREATE_VM: u64 = 0xae01;
const KVM_CHECK_EXTENSION: u64 = 0xae03;
const KVM_GET_VCPU_MMAP_SIZE: u64 = 0xae04;
const KVM_CREATE_VCPU: u64 = 0xae41;
const KVM_SET_TSS_ADDR: u64 = 0xae47;
const KVM_CREATE_IRQCHIP: u64 = 0xae60;
const KVM_RUN: u64 = 0xae80;
const KVM_SET_MP_STATE: u64 = 0x4004_ae99;
const KVM_SET_GSI_ROUTING: u64 = 0x4008_ae6a;
const KVM_SET_MSRS: u64 = 0x4008_ae89;
const KVM_SET_CPUID2: u64 = 0x4008_ae90;
const KVM_SET_DEVICE_ATTR: u64 = 0x4018_aee1;
const KVM_SET_USER_MEMORY_REGION: u64 = 0x4020_ae46;
const KVM_IRQFD: u64 = 0x4020_ae76;
const KVM_CREATE_PIT2: u64 = 0x4040_ae77;
const KVM_IOEVENTFD: u64 = 0x4040_ae79;
const KVM_ENABLE_CAP: u64 = 0x4068_aea3;
const KVM_SET_REGS: u64 = 0x4090_ae82;
const KVM_SET_SREGS: u64 = 0x4138_ae84;
const KVM_SET_XCRS: u64 = 0x4188_aea7;
const KVM_SET_FPU: u64 = 0x41a0_ae8d;
const KVM_SET_LAPIC: u64 = 0x4400_ae8f;
const KVM_SET_XSAVE: u64 = 0x5000_aea5;
const KVM_GET_MP_STATE: u64 = 0x8004_ae98;
const KVM_GET_VCPU_EVENTS: u64 = 0x8040_ae9f;
const KVM_GET_REGS: u64 = 0x8090_ae81;
const KVM_GET_SREGS: u64 = 0x8138_ae83;
const KVM_GET_XCRS: u64 = 0x8188_aea6;
const KVM_GET_FPU: u64 = 0x81a0_ae8c;
const KVM_GET_LAPIC: u64 = 0x8400_ae8e;
const KVM_GET_XSAVE: u64 = 0x9000_aea4;
const KVM_GET_SUPPORTED_CPUID: u64 = 0xc008_ae05;
const KVM_GET_MSRS: u64 = 0xc008_ae88;
const KVM_CREATE_DEVICE: u64 = 0xc00c_aee0;
// See include/uapi/linux/if_tun.h in the kernel code.
const TUNSETIFF: u64 = 0x4004_54ca;
const TUNSETOFFLOAD: u64 = 0x4004_54d0;
const TUNSETVNETHDRSZ: u64 = 0x4004_54d8;
const TUNGETFEATURES: u64 = 0x8004_54cf;
// See include/uapi/linux/sockios.h in the kernel code.
const SIOCSIFFLAGS: u64 = 0x8914;
const SIOCSIFADDR: u64 = 0x8916;
const SIOCSIFNETMASK: u64 = 0x891c;
// See include/uapi/linux/vfio.h in the kernel code.
const VFIO_GET_API_VERSION: u64 = 0x3b64;
const VFIO_CHECK_EXTENSION: u64 = 0x3b65;
const VFIO_SET_IOMMU: u64 = 0x3b66;
const VFIO_GROUP_GET_STATUS: u64 = 0x3b67;
const VFIO_GROUP_SET_CONTAINER: u64 = 0x3b68;
const VFIO_GROUP_UNSET_CONTAINER: u64 = 0x3b69;
const VFIO_GROUP_GET_DEVICE_FD: u64 = 0x3b6a;
const VFIO_DEVICE_GET_INFO: u64 = 0x3b6b;
const VFIO_DEVICE_GET_REGION_INFO: u64 = 0x3b6c;
const VFIO_DEVICE_GET_IRQ_INFO: u64 = 0x3b6d;
const VFIO_DEVICE_SET_IRQS: u64 = 0x3b6e;
const VFIO_DEVICE_RESET: u64 = 0x3b6f;
const VFIO_IOMMU_MAP_DMA: u64 = 0x3b71;
const VFIO_IOMMU_UNMAP_DMA: u64 = 0x3b72;
const VFIO_DEVICE_IOEVENTFD: u64 = 0x3b74;
fn create_vmm_ioctl_seccomp_rule() -> Result<Vec<SeccompRule>, Error> {
Ok(or![
and![Cond::new(1, ArgLen::DWORD, Eq, FIOCLEX)?],
and![Cond::new(1, ArgLen::DWORD, Eq, FIONBIO)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_CHECK_EXTENSION,)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_CREATE_DEVICE,)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_CREATE_IRQCHIP,)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_CREATE_PIT2)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_CREATE_VCPU)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_CREATE_VM)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_ENABLE_CAP)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_API_VERSION,)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_FPU)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_LAPIC)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_MP_STATE)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_MSRS)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_REGS)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_SREGS)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_SUPPORTED_CPUID,)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_VCPU_EVENTS,)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_VCPU_MMAP_SIZE,)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_XSAVE,)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_XCRS,)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_IOEVENTFD)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_IRQFD)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_RUN)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_CPUID2)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_DEVICE_ATTR,)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_FPU)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_GSI_ROUTING)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_LAPIC)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_MP_STATE)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_MSRS)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_REGS)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_SREGS)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_TSS_ADDR,)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_USER_MEMORY_REGION,)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_XSAVE,)?],
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_XCRS,)?],
and![Cond::new(1, ArgLen::DWORD, Eq, SIOCSIFADDR)?],
and![Cond::new(1, ArgLen::DWORD, Eq, SIOCSIFFLAGS)?],
and![Cond::new(1, ArgLen::DWORD, Eq, SIOCSIFNETMASK)?],
and![Cond::new(1, ArgLen::DWORD, Eq, TCSETS)?],
and![Cond::new(1, ArgLen::DWORD, Eq, TCGETS)?],
and![Cond::new(1, ArgLen::DWORD, Eq, TIOCGWINSZ)?],
and![Cond::new(1, ArgLen::DWORD, Eq, TUNGETFEATURES)?],
and![Cond::new(1, ArgLen::DWORD, Eq, TUNSETIFF)?],
and![Cond::new(1, ArgLen::DWORD, Eq, TUNSETOFFLOAD)?],
and![Cond::new(1, ArgLen::DWORD, Eq, TUNSETVNETHDRSZ)?],
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_GET_API_VERSION)?],
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_CHECK_EXTENSION)?],
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_SET_IOMMU)?],
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_GROUP_GET_STATUS)?],
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_GROUP_SET_CONTAINER)?],
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_GROUP_UNSET_CONTAINER)?],
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_GROUP_GET_DEVICE_FD)?],
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_DEVICE_GET_INFO)?],
and![Cond::new(
1,
ArgLen::DWORD,
Eq,
VFIO_DEVICE_GET_REGION_INFO
)?],
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_DEVICE_GET_IRQ_INFO)?],
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_DEVICE_SET_IRQS)?],
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_DEVICE_RESET)?],
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_IOMMU_MAP_DMA)?],
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_IOMMU_UNMAP_DMA)?],
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_DEVICE_IOEVENTFD)?],
])
}
fn create_api_ioctl_seccomp_rule() -> Result<Vec<SeccompRule>, Error> {
Ok(or![and![Cond::new(1, ArgLen::DWORD, Eq, FIONBIO)?],])
}
/// The filter containing the white listed syscall rules required by the VMM to
/// function.
pub fn vmm_thread_filter() -> Result<SeccompFilter, Error> {
Ok(SeccompFilter::new(
vec![
allow_syscall(libc::SYS_accept4),
allow_syscall(libc::SYS_access),
allow_syscall(libc::SYS_arch_prctl),
allow_syscall(libc::SYS_bind),
allow_syscall(libc::SYS_brk),
allow_syscall(libc::SYS_clock_gettime),
allow_syscall(libc::SYS_clone),
allow_syscall(libc::SYS_close),
allow_syscall(libc::SYS_connect),
allow_syscall(libc::SYS_dup),
allow_syscall(libc::SYS_epoll_create1),
allow_syscall(libc::SYS_epoll_ctl),
allow_syscall(libc::SYS_epoll_pwait),
allow_syscall(libc::SYS_epoll_wait),
allow_syscall(libc::SYS_eventfd2),
allow_syscall(libc::SYS_execve),
allow_syscall(libc::SYS_exit),
allow_syscall(libc::SYS_exit_group),
allow_syscall(libc::SYS_fallocate),
allow_syscall(libc::SYS_fcntl),
allow_syscall(libc::SYS_fdatasync),
allow_syscall(libc::SYS_fork),
allow_syscall(libc::SYS_fstat),
allow_syscall(libc::SYS_fsync),
allow_syscall(libc::SYS_ftruncate),
allow_syscall(libc::SYS_futex),
allow_syscall(libc::SYS_getpid),
allow_syscall(libc::SYS_getrandom),
allow_syscall(libc::SYS_gettid),
allow_syscall(libc::SYS_gettimeofday),
allow_syscall(libc::SYS_getuid),
allow_syscall_if(libc::SYS_ioctl, create_vmm_ioctl_seccomp_rule()?),
allow_syscall(libc::SYS_listen),
allow_syscall(libc::SYS_lseek),
allow_syscall(libc::SYS_madvise),
allow_syscall(libc::SYS_memfd_create),
allow_syscall(libc::SYS_mmap),
allow_syscall(libc::SYS_mprotect),
allow_syscall(libc::SYS_mremap),
allow_syscall(libc::SYS_munmap),
allow_syscall(libc::SYS_nanosleep),
allow_syscall(libc::SYS_open),
allow_syscall(libc::SYS_openat),
allow_syscall(libc::SYS_pipe2),
allow_syscall(libc::SYS_prctl),
allow_syscall(libc::SYS_pread64),
allow_syscall(libc::SYS_prlimit64),
allow_syscall(libc::SYS_pwrite64),
allow_syscall(libc::SYS_read),
allow_syscall(libc::SYS_readlink),
allow_syscall(libc::SYS_recvfrom),
allow_syscall(libc::SYS_recvmsg),
allow_syscall(libc::SYS_restart_syscall),
allow_syscall(libc::SYS_rt_sigaction),
allow_syscall(libc::SYS_rt_sigprocmask),
allow_syscall(libc::SYS_rt_sigreturn),
allow_syscall(libc::SYS_sched_getaffinity),
allow_syscall(libc::SYS_sendmsg),
allow_syscall(libc::SYS_set_robust_list),
allow_syscall(libc::SYS_set_tid_address),
allow_syscall(libc::SYS_sigaltstack),
allow_syscall_if(
libc::SYS_socket,
or![
and![Cond::new(0, ArgLen::DWORD, Eq, libc::AF_UNIX as u64)?],
and![Cond::new(0, ArgLen::DWORD, Eq, libc::AF_INET as u64)?],
],
),
allow_syscall(libc::SYS_socketpair),
allow_syscall(libc::SYS_stat),
allow_syscall(libc::SYS_statx),
allow_syscall(libc::SYS_tgkill),
allow_syscall(libc::SYS_tkill),
allow_syscall(libc::SYS_unlink),
allow_syscall(libc::SYS_wait4),
allow_syscall(libc::SYS_write),
]
.into_iter()
.collect(),
SeccompAction::Trap,
)?)
}
/// The filter containing the white listed syscall rules required by the API to
/// function.
pub fn api_thread_filter() -> Result<SeccompFilter, Error> {
Ok(SeccompFilter::new(
vec![
allow_syscall(libc::SYS_accept4),
allow_syscall(libc::SYS_bind),
allow_syscall(libc::SYS_close),
allow_syscall(libc::SYS_dup),
allow_syscall(libc::SYS_epoll_create1),
allow_syscall(libc::SYS_epoll_ctl),
allow_syscall(libc::SYS_epoll_pwait),
allow_syscall(libc::SYS_epoll_wait),
allow_syscall(libc::SYS_exit),
allow_syscall(libc::SYS_futex),
allow_syscall(libc::SYS_getrandom),
allow_syscall_if(libc::SYS_ioctl, create_api_ioctl_seccomp_rule()?),
allow_syscall(libc::SYS_listen),
allow_syscall(libc::SYS_madvise),
allow_syscall(libc::SYS_munmap),
allow_syscall(libc::SYS_recvfrom),
allow_syscall(libc::SYS_sigaltstack),
allow_syscall(libc::SYS_socket),
allow_syscall(libc::SYS_write),
]
.into_iter()
.collect(),
SeccompAction::Trap,
)?)
}
/// Generate a BPF program based on a seccomp level value.
pub fn get_seccomp_filter(
seccomp_level: &SeccompLevel,
thread_type: Thread,
) -> Result<BpfProgram, SeccompError> {
let filter = match thread_type {
Thread::Vmm => vmm_thread_filter(),
Thread::Api => api_thread_filter(),
};
match *seccomp_level {
SeccompLevel::None => Ok(vec![]),
_ => filter
.and_then(|filter| filter.try_into())
.map_err(SeccompError::SeccompFilter),
}
}

View File

@@ -11,7 +11,6 @@
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
//
extern crate anyhow;
extern crate arch;
extern crate devices;
extern crate epoll;
@@ -26,40 +25,45 @@ extern crate vm_allocator;
extern crate vm_memory;
extern crate vm_virtio;
use crate::config::{DeviceConfig, VmConfig};
use crate::config::{
DeviceConfig, DiskConfig, FsConfig, HotplugMethod, NetConfig, PmemConfig, ValidationError,
VmConfig, VsockConfig,
};
use crate::cpu;
use crate::device_manager::{get_win_size, Console, DeviceManager, DeviceManagerError};
use crate::memory_manager::{get_host_cpu_phys_bits, Error as MemoryManagerError, MemoryManager};
use crate::memory_manager::{Error as MemoryManagerError, MemoryManager};
use crate::migration::{url_to_path, vm_config_from_snapshot, VM_SNAPSHOT_FILE};
use crate::{CPU_MANAGER_SNAPSHOT_ID, DEVICE_MANAGER_SNAPSHOT_ID, MEMORY_MANAGER_SNAPSHOT_ID};
use anyhow::anyhow;
use arch::{layout, BootProtocol, EntryPoint};
use arch::{BootProtocol, EntryPoint};
use devices::{ioapic, HotPlugNotificationFlags};
use kvm_bindings::{kvm_enable_cap, kvm_userspace_memory_region, KVM_CAP_SPLIT_IRQCHIP};
use kvm_ioctls::*;
use linux_loader::cmdline::Cmdline;
use linux_loader::loader::elf::Error::InvalidElfMagicNumber;
use linux_loader::loader::KernelLoader;
use signal_hook::{iterator::Signals, SIGINT, SIGTERM, SIGWINCH};
use std::convert::TryInto;
use std::ffi::CString;
use std::fs::File;
use std::io;
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::io::{self, Seek, SeekFrom};
use std::ops::Deref;
use std::path::PathBuf;
use std::sync::{Arc, Mutex, RwLock};
use std::{result, str, thread};
use vm_allocator::{GsiApic, SystemAllocator};
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
use url::Url;
use vm_memory::{
Address, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryMmap,
GuestMemoryRegion, GuestUsize,
GuestMemoryRegion,
};
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
Transportable,
};
use vmm_sys_util::eventfd::EventFd;
use vmm_sys_util::terminal::Terminal;
const X86_64_IRQ_BASE: u32 = 5;
// CPUID feature bits
const TSC_DEADLINE_TIMER_ECX_BIT: u8 = 24; // tsc deadline timer ecx bit.
const HYPERVISOR_ECX_BIT: u8 = 31; // Hypervisor ecx bit.
// 64 bit direct boot entry offset for bzImage
const KERNEL_64BIT_ENTRY_OFFSET: u64 = 0x200;
@@ -78,9 +82,15 @@ pub enum Error {
/// Cannot open the kernel image
KernelFile(io::Error),
/// Cannot open the initramfs image
InitramfsFile(io::Error),
/// Cannot load the kernel in memory
KernelLoad(linux_loader::loader::Error),
/// Cannot load the initramfs in memory
InitramfsLoad,
/// Cannot load the command line in memory
LoadCmdLine(linux_loader::loader::Error),
@@ -107,9 +117,6 @@ pub enum Error {
/// Cannot setup terminal in canonical mode.
SetTerminalCanon(vmm_sys_util::errno::Error),
/// Cannot create the system allocator
CreateSystemAllocator,
/// Failed parsing network parameters
ParseNetworkParameters,
@@ -131,6 +138,9 @@ pub enum Error {
/// VM is not created
VmNotCreated,
/// VM is already created
VmAlreadyCreated,
/// VM is not running
VmNotRunning,
@@ -169,6 +179,27 @@ pub enum Error {
/// No PCI support
NoPciSupport,
/// Eventfd write error
EventfdError(std::io::Error),
/// Cannot snapshot VM
Snapshot(MigratableError),
/// Cannot restore VM
Restore(MigratableError),
/// Cannot send VM snapshot
SnapshotSend(MigratableError),
/// Cannot convert source URL from Path into &str
RestoreSourceUrlPathToStr,
/// Failed to validate config
ConfigValidation(ValidationError),
/// No more that one virtio-vsock device
TooManyVsockDevices,
}
pub type Result<T> = result::Result<T, Error>;
@@ -216,6 +247,7 @@ impl VmState {
pub struct Vm {
kernel: File,
initramfs: Option<File>,
threads: Vec<thread::JoinHandle<()>>,
device_manager: Arc<Mutex<DeviceManager>>,
config: Arc<Mutex<VmConfig>>,
@@ -227,12 +259,7 @@ pub struct Vm {
}
impl Vm {
pub fn new(
config: Arc<Mutex<VmConfig>>,
exit_evt: EventFd,
reset_evt: EventFd,
vmm_path: PathBuf,
) -> Result<Self> {
fn kvm_new() -> Result<(Kvm, Arc<VmFd>)> {
let kvm = Kvm::new().map_err(Error::KvmNew)?;
// Check required capabilities:
@@ -248,9 +275,6 @@ impl Vm {
return Err(Error::CapabilityMissing(Cap::SplitIrqchip));
}
let kernel = File::open(&config.lock().unwrap().kernel.as_ref().unwrap().path)
.map_err(Error::KernelFile)?;
let fd: VmFd;
loop {
match kvm.create_vm() {
@@ -274,7 +298,6 @@ impl Vm {
fd.set_tss_address(arch::x86_64::layout::KVM_TSS_ADDRESS.raw_value() as usize)
.map_err(Error::VmSetup)?;
let mut cpuid_patches = Vec::new();
// Create split irqchip
// Only the local APIC is emulated in kernel, both PICs and IOAPIC
// are not.
@@ -283,72 +306,27 @@ impl Vm {
cap.args[0] = ioapic::NUM_IOAPIC_PINS as u64;
fd.enable_cap(&cap).map_err(Error::VmSetup)?;
// Patch tsc deadline timer bit
cpuid_patches.push(cpu::CpuidPatch {
function: 1,
index: 0,
flags_bit: None,
eax_bit: None,
ebx_bit: None,
ecx_bit: Some(TSC_DEADLINE_TIMER_ECX_BIT),
edx_bit: None,
});
Ok((kvm, fd))
}
// Patch hypervisor bit
cpuid_patches.push(cpu::CpuidPatch {
function: 1,
index: 0,
flags_bit: None,
eax_bit: None,
ebx_bit: None,
ecx_bit: Some(HYPERVISOR_ECX_BIT),
edx_bit: None,
});
// Supported CPUID
let mut cpuid = kvm
.get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES)
.map_err(Error::VmSetup)?;
cpu::CpuidPatch::patch_cpuid(&mut cpuid, cpuid_patches);
let ioapic = GsiApic::new(
X86_64_IRQ_BASE,
ioapic::NUM_IOAPIC_PINS as u32 - X86_64_IRQ_BASE,
);
// Let's allocate 64 GiB of addressable MMIO space, starting at 0.
let allocator = Arc::new(Mutex::new(
SystemAllocator::new(
GuestAddress(0),
1 << 16 as GuestUsize,
GuestAddress(0),
1 << get_host_cpu_phys_bits(),
layout::MEM_32BIT_RESERVED_START,
layout::MEM_32BIT_DEVICES_SIZE,
vec![ioapic],
)
.ok_or(Error::CreateSystemAllocator)?,
));
let memory_config = config.lock().unwrap().memory.clone();
let memory_manager = MemoryManager::new(
allocator.clone(),
fd.clone(),
memory_config.size,
memory_config.hotplug_size,
&memory_config.file,
memory_config.mergeable,
)
.map_err(Error::MemoryManager)?;
let guest_memory = memory_manager.lock().unwrap().guest_memory();
fn new_from_memory_manager(
config: Arc<Mutex<VmConfig>>,
memory_manager: Arc<Mutex<MemoryManager>>,
fd: Arc<VmFd>,
kvm: Kvm,
exit_evt: EventFd,
reset_evt: EventFd,
vmm_path: PathBuf,
) -> Result<Self> {
config
.lock()
.unwrap()
.validate()
.map_err(Error::ConfigValidation)?;
let device_manager = DeviceManager::new(
fd.clone(),
config.clone(),
allocator,
memory_manager.clone(),
&exit_evt,
&reset_evt,
@@ -356,23 +334,32 @@ impl Vm {
)
.map_err(Error::DeviceManager)?;
let on_tty = unsafe { libc::isatty(libc::STDIN_FILENO as i32) } != 0;
let boot_vcpus = config.lock().unwrap().cpus.boot_vcpus;
let max_vcpus = config.lock().unwrap().cpus.max_vcpus;
let cpu_manager = cpu::CpuManager::new(
boot_vcpus,
max_vcpus,
&config.lock().unwrap().cpus.clone(),
&device_manager,
guest_memory,
memory_manager.lock().unwrap().guest_memory(),
&kvm,
fd,
cpuid,
reset_evt,
)
.map_err(Error::CpuManager)?;
let on_tty = unsafe { libc::isatty(libc::STDIN_FILENO as i32) } != 0;
let kernel = File::open(&config.lock().unwrap().kernel.as_ref().unwrap().path)
.map_err(Error::KernelFile)?;
let initramfs = config
.lock()
.unwrap()
.initramfs
.as_ref()
.map(|i| File::open(&i.path))
.transpose()
.map_err(Error::InitramfsFile)?;
Ok(Vm {
kernel,
initramfs,
device_manager,
config,
on_tty,
@@ -384,6 +371,93 @@ impl Vm {
})
}
pub fn new(
config: Arc<Mutex<VmConfig>>,
exit_evt: EventFd,
reset_evt: EventFd,
vmm_path: PathBuf,
) -> Result<Self> {
let (kvm, fd) = Vm::kvm_new()?;
let memory_manager = MemoryManager::new(
fd.clone(),
&config.lock().unwrap().memory.clone(),
None,
false,
)
.map_err(Error::MemoryManager)?;
Vm::new_from_memory_manager(
config,
memory_manager,
fd,
kvm,
exit_evt,
reset_evt,
vmm_path,
)
}
pub fn new_from_snapshot(
snapshot: &Snapshot,
exit_evt: EventFd,
reset_evt: EventFd,
vmm_path: PathBuf,
source_url: &str,
prefault: bool,
) -> Result<Self> {
let (kvm, fd) = Vm::kvm_new()?;
let config = vm_config_from_snapshot(snapshot).map_err(Error::Restore)?;
let memory_manager = if let Some(memory_manager_snapshot) =
snapshot.snapshots.get(MEMORY_MANAGER_SNAPSHOT_ID)
{
MemoryManager::new_from_snapshot(
memory_manager_snapshot,
fd.clone(),
&config.lock().unwrap().memory.clone(),
source_url,
prefault,
)
.map_err(Error::MemoryManager)?
} else {
return Err(Error::Restore(MigratableError::Restore(anyhow!(
"Missing memory manager snapshot"
))));
};
Vm::new_from_memory_manager(
config,
memory_manager,
fd,
kvm,
exit_evt,
reset_evt,
vmm_path,
)
}
fn load_initramfs(&mut self, guest_mem: &GuestMemoryMmap) -> Result<arch::InitramfsConfig> {
let mut initramfs = self.initramfs.as_ref().unwrap();
let size: usize = initramfs
.seek(SeekFrom::End(0))
.map_err(|_| Error::InitramfsLoad)?
.try_into()
.unwrap();
initramfs
.seek(SeekFrom::Start(0))
.map_err(|_| Error::InitramfsLoad)?;
let address =
arch::initramfs_load_addr(guest_mem, size).map_err(|_| Error::InitramfsLoad)?;
let address = GuestAddress(address);
guest_mem
.read_from(address, &mut initramfs, size)
.map_err(|_| Error::InitramfsLoad)?;
Ok(arch::InitramfsConfig { address, size })
}
fn load_kernel(&mut self) -> Result<EntryPoint> {
let mut cmdline = Cmdline::new(arch::CMDLINE_MAX_SIZE);
cmdline
@@ -396,15 +470,15 @@ impl Vm {
let cmdline_cstring = CString::new(cmdline).map_err(Error::CmdLineCString)?;
let guest_memory = self.memory_manager.lock().as_ref().unwrap().guest_memory();
let mem = guest_memory.memory();
let entry_addr = match linux_loader::loader::Elf::load(
let entry_addr = match linux_loader::loader::elf::Elf::load(
mem.deref(),
None,
&mut self.kernel,
Some(arch::layout::HIGH_RAM_START),
) {
Ok(entry_addr) => entry_addr,
Err(linux_loader::loader::Error::InvalidElfMagicNumber) => {
linux_loader::loader::BzImage::load(
Err(linux_loader::loader::Error::Elf(InvalidElfMagicNumber)) => {
linux_loader::loader::bzimage::BzImage::load(
mem.deref(),
None,
&mut self.kernel,
@@ -412,7 +486,9 @@ impl Vm {
)
.map_err(Error::KernelLoad)?
}
_ => panic!("Invalid elf file"),
Err(e) => {
return Err(Error::KernelLoad(e));
}
};
linux_loader::loader::load_cmdline(
@@ -421,6 +497,12 @@ impl Vm {
&cmdline_cstring,
)
.map_err(Error::LoadCmdLine)?;
let initramfs_config = match self.initramfs {
Some(_) => Some(self.load_initramfs(mem.deref())?),
None => None,
};
let boot_vcpus = self.cpu_manager.lock().unwrap().boot_vcpus();
let _max_vcpus = self.cpu_manager.lock().unwrap().max_vcpus();
@@ -443,6 +525,7 @@ impl Vm {
&mem,
arch::layout::CMDLINE_START,
cmdline_cstring.to_bytes().len() + 1,
&initramfs_config,
boot_vcpus,
Some(hdr),
rsdp_addr,
@@ -462,21 +545,24 @@ impl Vm {
})
}
None => {
// Assume by default Linux boot protocol is used and not PVH
let mut entry_point_addr: GuestAddress = entry_addr.kernel_load;
let entry_point_addr: GuestAddress;
let boot_prot: BootProtocol;
let boot_prot = if entry_addr.pvh_entry_addr.is_some() {
// entry_addr.pvh_entry_addr field is safe to unwrap here
entry_point_addr = entry_addr.pvh_entry_addr.unwrap();
BootProtocol::PvhBoot
if let Some(pvh_entry_addr) = entry_addr.pvh_entry_addr {
// Use the PVH kernel entry point to boot the guest
entry_point_addr = pvh_entry_addr;
boot_prot = BootProtocol::PvhBoot;
} else {
BootProtocol::LinuxBoot
};
// Use the Linux 64-bit boot protocol
entry_point_addr = entry_addr.kernel_load;
boot_prot = BootProtocol::LinuxBoot;
}
arch::configure_system(
&mem,
arch::layout::CMDLINE_START,
cmdline_cstring.to_bytes().len() + 1,
&initramfs_config,
boot_vcpus,
None,
rsdp_addr,
@@ -493,10 +579,15 @@ impl Vm {
}
pub fn shutdown(&mut self) -> Result<()> {
let mut state = self.state.try_write().map_err(|_| Error::PoisonedState)?;
let current_state = self.get_state()?;
let new_state = VmState::Shutdown;
state.valid_transition(new_state)?;
current_state.valid_transition(new_state)?;
// If the current state is paused that means most of the handles got killed by pthread_kill
// We need to unpark those threads by calling resume
if current_state == VmState::Paused {
self.resume().map_err(Error::Resume)?;
}
if self.on_tty {
// Don't forget to set the terminal in canonical mode
@@ -522,6 +613,8 @@ impl Vm {
for thread in self.threads.drain(..) {
thread.join().map_err(Error::ThreadCleanup)?
}
let mut state = self.state.try_write().map_err(|_| Error::PoisonedState)?;
*state = new_state;
Ok(())
@@ -546,23 +639,36 @@ impl Vm {
}
if let Some(desired_memory) = desired_memory {
if self
let new_region = self
.memory_manager
.lock()
.unwrap()
.resize(desired_memory)
.map_err(Error::MemoryManager)?
{
.map_err(Error::MemoryManager)?;
if let Some(new_region) = &new_region {
self.device_manager
.lock()
.unwrap()
.notify_hotplug(HotPlugNotificationFlags::MEMORY_DEVICES_CHANGED)
.update_memory(&new_region)
.map_err(Error::DeviceManager)?;
let memory_config = &self.config.lock().unwrap().memory;
match memory_config.hotplug_method {
HotplugMethod::Acpi => {
self.device_manager
.lock()
.unwrap()
.notify_hotplug(HotPlugNotificationFlags::MEMORY_DEVICES_CHANGED)
.map_err(Error::DeviceManager)?;
}
HotplugMethod::VirtioMem => {}
}
}
// We update the VM config regardless of the actual guest resize operation
// result (true or false, happened or not), so that if the VM reboots it
// will be running with the last configure memory size.
// We update the VM config regardless of the actual guest resize
// operation result (happened or not), so that if the VM reboots
// it will be running with the last configure memory size.
self.config.lock().unwrap().memory.size = desired_memory;
}
Ok(())
@@ -615,15 +721,198 @@ impl Vm {
// ensure the device would not be created in case of a reboot.
{
let mut config = self.config.lock().unwrap();
// Remove if VFIO device
if let Some(devices) = config.devices.as_mut() {
devices.retain(|dev| {
if let Some(dev_id) = &dev.id {
*dev_id != _id
} else {
true
}
});
devices.retain(|dev| dev.id.as_ref() != Some(&_id));
}
// Remove if disk device
if let Some(disks) = config.disks.as_mut() {
disks.retain(|dev| dev.id.as_ref() != Some(&_id));
}
// Remove if net device
if let Some(net) = config.net.as_mut() {
net.retain(|dev| dev.id.as_ref() != Some(&_id));
}
// Remove if pmem device
if let Some(pmem) = config.pmem.as_mut() {
pmem.retain(|dev| dev.id.as_ref() != Some(&_id));
}
// Remove if vsock device
if let Some(vsock) = config.vsock.as_ref() {
if vsock.id.as_ref() == Some(&_id) {
config.vsock = None;
}
}
}
self.device_manager
.lock()
.unwrap()
.notify_hotplug(HotPlugNotificationFlags::PCI_DEVICES_CHANGED)
.map_err(Error::DeviceManager)?;
}
Ok(())
} else {
Err(Error::NoPciSupport)
}
}
pub fn add_disk(&mut self, mut _disk_cfg: DiskConfig) -> Result<()> {
if cfg!(feature = "pci_support") {
#[cfg(feature = "pci_support")]
{
self.device_manager
.lock()
.unwrap()
.add_disk(&mut _disk_cfg)
.map_err(Error::DeviceManager)?;
// Update VmConfig by adding the new device. This is important to
// ensure the device would be created in case of a reboot.
{
let mut config = self.config.lock().unwrap();
if let Some(disks) = config.disks.as_mut() {
disks.push(_disk_cfg);
} else {
config.disks = Some(vec![_disk_cfg]);
}
}
self.device_manager
.lock()
.unwrap()
.notify_hotplug(HotPlugNotificationFlags::PCI_DEVICES_CHANGED)
.map_err(Error::DeviceManager)?;
}
Ok(())
} else {
Err(Error::NoPciSupport)
}
}
pub fn add_fs(&mut self, mut _fs_cfg: FsConfig) -> Result<()> {
if cfg!(feature = "pci_support") {
#[cfg(feature = "pci_support")]
{
self.device_manager
.lock()
.unwrap()
.add_fs(&mut _fs_cfg)
.map_err(Error::DeviceManager)?;
// Update VmConfig by adding the new device. This is important to
// ensure the device would be created in case of a reboot.
{
let mut config = self.config.lock().unwrap();
if let Some(fs_config) = config.fs.as_mut() {
fs_config.push(_fs_cfg);
} else {
config.fs = Some(vec![_fs_cfg]);
}
}
self.device_manager
.lock()
.unwrap()
.notify_hotplug(HotPlugNotificationFlags::PCI_DEVICES_CHANGED)
.map_err(Error::DeviceManager)?;
}
Ok(())
} else {
Err(Error::NoPciSupport)
}
}
pub fn add_pmem(&mut self, mut _pmem_cfg: PmemConfig) -> Result<()> {
if cfg!(feature = "pci_support") {
#[cfg(feature = "pci_support")]
{
self.device_manager
.lock()
.unwrap()
.add_pmem(&mut _pmem_cfg)
.map_err(Error::DeviceManager)?;
// Update VmConfig by adding the new device. This is important to
// ensure the device would be created in case of a reboot.
{
let mut config = self.config.lock().unwrap();
if let Some(pmem) = config.pmem.as_mut() {
pmem.push(_pmem_cfg);
} else {
config.pmem = Some(vec![_pmem_cfg]);
}
}
self.device_manager
.lock()
.unwrap()
.notify_hotplug(HotPlugNotificationFlags::PCI_DEVICES_CHANGED)
.map_err(Error::DeviceManager)?;
}
Ok(())
} else {
Err(Error::NoPciSupport)
}
}
pub fn add_net(&mut self, mut _net_cfg: NetConfig) -> Result<()> {
if cfg!(feature = "pci_support") {
#[cfg(feature = "pci_support")]
{
self.device_manager
.lock()
.unwrap()
.add_net(&mut _net_cfg)
.map_err(Error::DeviceManager)?;
// Update VmConfig by adding the new device. This is important to
// ensure the device would be created in case of a reboot.
{
let mut config = self.config.lock().unwrap();
if let Some(net) = config.net.as_mut() {
net.push(_net_cfg);
} else {
config.net = Some(vec![_net_cfg]);
}
}
self.device_manager
.lock()
.unwrap()
.notify_hotplug(HotPlugNotificationFlags::PCI_DEVICES_CHANGED)
.map_err(Error::DeviceManager)?;
}
Ok(())
} else {
Err(Error::NoPciSupport)
}
}
pub fn add_vsock(&mut self, mut _vsock_cfg: VsockConfig) -> Result<()> {
if cfg!(feature = "pci_support") {
#[cfg(feature = "pci_support")]
{
if self.config.lock().unwrap().vsock.is_some() {
return Err(Error::TooManyVsockDevices);
}
self.device_manager
.lock()
.unwrap()
.add_vsock(&mut _vsock_cfg)
.map_err(Error::DeviceManager)?;
// Update VmConfig by adding the new device. This is important to
// ensure the device would be created in case of a reboot.
{
let mut config = self.config.lock().unwrap();
config.vsock = Some(_vsock_cfg);
}
self.device_manager
@@ -794,7 +1083,187 @@ impl Pausable for Vm {
}
}
impl Snapshotable for Vm {}
#[derive(Serialize, Deserialize)]
pub struct VmSnapshot {
pub config: Arc<Mutex<VmConfig>>,
}
pub const VM_SNAPSHOT_ID: &str = "vm";
impl Snapshottable for Vm {
fn id(&self) -> String {
VM_SNAPSHOT_ID.to_string()
}
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
let current_state = self.get_state().unwrap();
if current_state != VmState::Paused {
return Err(MigratableError::Snapshot(anyhow!(
"Trying to snapshot while VM is running"
)));
}
let mut vm_snapshot = Snapshot::new(VM_SNAPSHOT_ID);
let vm_snapshot_data = serde_json::to_vec(&VmSnapshot {
config: self.get_config(),
})
.map_err(|e| MigratableError::Snapshot(e.into()))?;
vm_snapshot.add_snapshot(self.cpu_manager.lock().unwrap().snapshot()?);
vm_snapshot.add_snapshot(self.memory_manager.lock().unwrap().snapshot()?);
vm_snapshot.add_snapshot(self.device_manager.lock().unwrap().snapshot()?);
vm_snapshot.add_data_section(SnapshotDataSection {
id: format!("{}-section", VM_SNAPSHOT_ID),
snapshot: vm_snapshot_data,
});
Ok(vm_snapshot)
}
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
let current_state = self
.get_state()
.map_err(|e| MigratableError::Restore(anyhow!("Could not get VM state: {:#?}", e)))?;
let new_state = VmState::Running;
current_state.valid_transition(new_state).map_err(|e| {
MigratableError::Restore(anyhow!("Could not restore VM state: {:#?}", e))
})?;
if let Some(memory_manager_snapshot) = snapshot.snapshots.get(MEMORY_MANAGER_SNAPSHOT_ID) {
self.memory_manager
.lock()
.unwrap()
.restore(*memory_manager_snapshot.clone())?;
} else {
return Err(MigratableError::Restore(anyhow!(
"Missing memory manager snapshot"
)));
}
if let Some(device_manager_snapshot) = snapshot.snapshots.get(DEVICE_MANAGER_SNAPSHOT_ID) {
self.device_manager
.lock()
.unwrap()
.restore(*device_manager_snapshot.clone())?;
} else {
return Err(MigratableError::Restore(anyhow!(
"Missing device manager snapshot"
)));
}
if let Some(cpu_manager_snapshot) = snapshot.snapshots.get(CPU_MANAGER_SNAPSHOT_ID) {
self.cpu_manager
.lock()
.unwrap()
.restore(*cpu_manager_snapshot.clone())?;
} else {
return Err(MigratableError::Restore(anyhow!(
"Missing CPU manager snapshot"
)));
}
if self
.device_manager
.lock()
.unwrap()
.console()
.input_enabled()
{
let console = self.device_manager.lock().unwrap().console().clone();
let signals = Signals::new(&[SIGWINCH, SIGINT, SIGTERM]);
match signals {
Ok(signals) => {
self.signals = Some(signals.clone());
let on_tty = self.on_tty;
self.threads.push(
thread::Builder::new()
.name("signal_handler".to_string())
.spawn(move || Vm::os_signal_handler(signals, console, on_tty))
.map_err(|e| {
MigratableError::Restore(anyhow!(
"Could not start console signal thread: {:#?}",
e
))
})?,
);
}
Err(e) => error!("Signal not found {}", e),
}
if self.on_tty {
io::stdin().lock().set_raw_mode().map_err(|e| {
MigratableError::Restore(anyhow!(
"Could not set terminal in raw mode: {:#?}",
e
))
})?;
}
}
let mut state = self
.state
.try_write()
.map_err(|e| MigratableError::Restore(anyhow!("Could not set VM state: {:#?}", e)))?;
*state = new_state;
Ok(())
}
}
impl Transportable for Vm {
fn send(
&self,
snapshot: &Snapshot,
destination_url: &str,
) -> std::result::Result<(), MigratableError> {
let url = Url::parse(destination_url).map_err(|e| {
MigratableError::MigrateSend(anyhow!("Could not parse destination URL: {}", e))
})?;
match url.scheme() {
"file" => {
let mut vm_snapshot_path = url_to_path(&url)?;
vm_snapshot_path.push(VM_SNAPSHOT_FILE);
// Create the snapshot file
let mut vm_snapshot_file = OpenOptions::new()
.read(true)
.write(true)
.create_new(true)
.open(vm_snapshot_path)
.map_err(|e| MigratableError::MigrateSend(e.into()))?;
// Serialize and write the snapshot
let vm_snapshot = serde_json::to_vec(snapshot)
.map_err(|e| MigratableError::MigrateSend(e.into()))?;
vm_snapshot_file
.write(&vm_snapshot)
.map_err(|e| MigratableError::MigrateSend(e.into()))?;
// Tell the memory manager to also send/write its own snapshot.
if let Some(memory_manager_snapshot) =
snapshot.snapshots.get(MEMORY_MANAGER_SNAPSHOT_ID)
{
self.memory_manager
.lock()
.unwrap()
.send(&*memory_manager_snapshot.clone(), destination_url)?;
} else {
return Err(MigratableError::Restore(anyhow!(
"Missing memory manager snapshot"
)));
}
}
_ => {
return Err(MigratableError::MigrateSend(anyhow!(
"Unsupported VM transport URL scheme: {}",
url.scheme()
)))
}
}
Ok(())
}
}
impl Migratable for Vm {}
#[cfg(test)]